Developer Tutorials
Video Template Validation: Test Input States Before Rendering
Run a complete Node.js example to check template variables, scene order, timing and failures, then render and review one release-status video.
By Mina RamzyPublished Updated
A template can produce valid JSON and still show the wrong title, keep an unwanted call to action, or turn an empty list into an awkward video. Test the data and the resolved sequence before you spend credits, then review the actual video for visual problems.
The Node.js example below checks six valid input states and six deliberate failures against one release-status template. Its default command stops after validation. A separate command renders one representative video and downloads it.
See what changes when the input changes
Watch the same release-status template resolve to four, three, two, or just one scene as its inputs change. Emptying the list or switching the call to action (CTA) off changes the result. This 48-second walkthrough also shows a rejected input type and the checks to run before reviewing an actual render.
Read the video transcript
One good-looking render does not prove that every input works.
Start with the same release-status template: a title, two items, and a call to action. The usual input resolves to four scenes.
Empty the list: the status scenes disappear. Turn the call to action off as well: only the intro remains.
Keep the items, but switch the call to action off: three scenes remain.
Now pass the word false as a string. Our input check rejects it. The Boolean false is the value we want.
Check the resolved text, scene order, and duration before rendering. These checks use no render credits.
Then watch an actual render. Valid structure still needs visual review.
The runnable example below produces the release-status video shown at the end of this walkthrough.
There are three different questions to answer:
- 1. Check the inputDoes the data meet your application's rules?
Types, names, boundaries - 2. Inspect the projectDoes Zvid resolve the intended content and sequence?
Values, scenes, duration - 3. Review the videoIs the finished result readable and well timed?
Wrapping, contrast, transitions
Run the checks without rendering
You need Node.js 22 or newer with npm, a Zvid account, and a key from app.zvid.io/api-keys. Run this on your computer or server, where the key can stay private. Your account's current limits must allow the sample's 1280 × 720 canvas and the matrix's longest duration of 13 seconds.
Validation and template preview do not consume render credits. Rendering the representative video is estimated at 11 render credits. Check the current estimate before submitting it; the included script refuses a render above its 11-credit ceiling.
The sample sends its template file inline as payload. It does not create a saved template in your account or need an existing template ID. Zvid resolves variables and placeholders for inline projects as well as stored templates.
1. Get the complete example
Extract the ZIP and open a terminal inside its video-template-validation folder, beside package.json. Run:
node --version
npm ci
npm run check
npm test
The ten tests run offline without credentials or credits. In PowerShell, use npm.cmd instead of npm if your execution policy blocks npm.ps1.
| File | What you will use it for |
|---|---|
data/template.json |
The complete design, default variables, repeated status scene, and conditional CTA |
data/cases.json |
Named input states and their expected results |
src/preflight.js |
Input rules and checks for resolved content, scene order, count, and duration |
src/index.js |
Validation by default, plus explicit render and resume commands |
out/ |
Your validation responses, matrix summary, and any saved render job and video |
2. Add your API key
Copy .env.example to .env in the same folder. In PowerShell, run Copy-Item .env.example .env. On macOS or Linux, run cp .env.example .env.
Open .env in a text editor, paste your key after ZVID_API_KEY=, and keep the credit ceiling:
ZVID_API_KEY=YOUR_API_KEY
MAX_RENDER_CREDITS=11
The npm commands load this file. Keep it out of source control, shared downloads, and browser code. The authentication guide explains API-key management.
3. Run the matrix
npm start
The script sends each valid case to POST https://api.zvid.io/api/render/validate/api-key. This endpoint resolves the template data and validates the resulting project against the current account limits. It returns the resolved payload, warnings, and creditsRequired, with no render job.
Expect a PASS line for every case, followed by:
Representative render estimate: 11 credits.
Matrix passed. No render submitted.
Open out/matrix.json for the summary. Open out/representative-validation.json to inspect the actual resolved project and estimate. A later run replaces the validation files, so copy them first if you want to compare two designs.
The script exits with an error if a valid case fails, a deliberately bad case is unexpectedly accepted, or a response does not match the expected content and structure. An authentication or network failure never counts as a successful negative test.
What the input states are testing
Each fixture is a named set of inputs with an expected outcome. This is the representative set from data/cases.json:
{
"title": "October release status",
"items": [
{ "label": "API", "value": "Ready" },
{ "label": "Editor", "value": "Ready" }
],
Collapse example
{
"title": "October release status",
"items": [
{ "label": "API", "value": "Ready" },
{ "label": "Editor", "value": "Ready" }
],
"showCta": true,
"cta": "Review the final render"
}
The design uses {{title}} for the introduction. Its status scene has "iterate": "items" and "iterateAs": "item", so each item becomes a scene containing {{item.label}} and {{item.value}}. The final scene uses "condition": "{{showCta}}". A Boolean false removes that scene.
These are iteration and condition rules, not an expression language. Calculate business decisions in your application, then pass the resulting Boolean. Use false, not the string "false".
| Valid case | Change from the representative input | Expected sequence |
|---|---|---|
| Representative | Two items, CTA on | 4 scenes, 10.5 seconds |
| Long copy | Longer title, labels, and status values | 4 scenes, 10.5 seconds |
| Empty items | items: [], CTA still on |
Intro and CTA only: 2 scenes, 5.5 seconds |
| CTA off | Two items, showCta: false |
Intro and two statuses: 3 scenes, 8 seconds |
| Minimum | Empty items and CTA off | Intro only: 1 scene, 3 seconds |
| Maximum items | Three items, CTA on | 5 scenes, 13 seconds |
The longest-copy fixture checks that those strings resolve unchanged. It does not prove that they fit visually. Review the long-copy layout when approving or changing the design.
The sample's input rules allow at most three items, a 64-character title, 28-character labels, 20-character status values, and a 48-character CTA. They reject empty text, HTML, template braces, unknown fields, and a non-Boolean showCta. These are application choices to keep this example predictable, not Zvid-wide limits or universal typography rules.
Four fixtures must fail those local rules: a string Boolean, an unknown variable, four items, and a 65-character title. Two additional fixtures deliberately bypass the local checks to confirm Zvid's resolution errors:
| API failure case | Expected result |
|---|---|
items is the string "API" |
HTTP 400 with a field path containing iterate |
An item has label but no value |
HTTP 400 with a text-field error for the unresolved {{item.value}} |
Their response details are saved in out/wrong-items-type-error.json and out/missing-item-value-error.json. Normal production inputs should pass your own rules first. The data-and-design contract example explains that separation in more detail.
Check the result, not just a valid flag
For each accepted case, the script checks the actual title, status text, CTA, scene order, count, and duration. Checking content matters: a template can still resolve successfully after a designer renames a placeholder and leaves a plausible default behind.
For example, this is the central sequence check from the supplied helper, with the input named variables:
const ids = [
'intro',
...variables.items.map((_, i) => `status-${i + 1}`),
...(variables.showCta ? ['cta'] : []),
];
assert.deepEqual(project.scenes.map(scene => scene.id), ids);
Duration has its own expectation. With these explicit three-second scenes and half-second fades, use the sum of scene durations minus the actual overlaps. Four scenes produce 12 - 1.5 = 10.5 seconds. The scene timing guide explains why transitions shorten the combined timeline.
The sample also stops on every warning returned by request validation. That is this example's acceptance policy. In a production system, investigate warnings and document any intentional exception rather than silently ignoring them.
If you already have a saved template
Use the stored-template preview endpoint to inspect a dataset against its current design:
POST /api/templates/{id}/preview
x-api-key: YOUR_API_KEY
Content-Type: application/json
{"variables":{"title":"October release status","items":[],"showCta":false,"cta":"Review the final render"}}
Replace {id} with your saved template ID. This shows the HTTP request shape; the runnable download uses inline validation instead.
A successful template preview returns HTTP 200 with project and stats. It is a JSON dry run: it does not queue a preview video, return a job ID, or generate a media URL. Check project.scenes, project.duration, and its resolved text. To obtain warnings and a credit estimate, send that resolved project to the validation endpoint under payload.
| Operation | Useful response fields | Creates a video? |
|---|---|---|
| Validate an inline or stored-template request | valid, payload, warnings, creditsRequired |
No |
| Preview a stored template | project, stats |
No |
| Submit a render | jobId, then a completed job with an output URL |
Yes, after completion |
Keep a copy of the checked template and fixtures with your application release. An existing template ID points to its current saved design; an old successful check does not approve later edits.
Render one case and inspect the video
Once the matrix passes and you accept the current credit estimate, run:
npm run render
This command spends render credits. It reruns the matrix, checks the representative estimate against MAX_RENDER_CREDITS, submits only that case, and saves its job ID before tracking completion. It renders the resolved snapshot from validation, keeping the checked content and design together.
When the job completes, the script downloads out/release-status.mp4. It also saves out/request.json, out/run.json, and out/job.json so you can inspect what was submitted and which job produced the result.
This is the representative output: an introduction, two repeated status scenes, and a final call to action (CTA). Each scene lasts three seconds. Three half-second fades overlap the scene boundaries, making the finished video 10.5 seconds, not 12.
Watch the whole video. Confirm the title, both statuses, and the CTA are correct. Check text wrapping, contrast, fades, reading time, and the final frame. A completed job proves that an output was produced, not that its design is approved.
The command renders one representative case, not the entire matrix. Before using your own template in production, visually review any state that changes the layout, especially long copy, different fonts or languages, an empty list, and a removed CTA. Add further renders where those changes cannot be approved from an editor preview. Each additional render consumes credits.
Resume an existing job after an interruption
npm run resume
Resume reads the saved ID, tracks the same job, and downloads its result. It does not submit another render. The script waits for state: "completed" and a usable output URL; progress: 100 alone is not completion. A failed job stops with its failedReason.
If the render command stops before saving an ID, check Renders at app.zvid.io. A missing response does not prove that the job was never submitted. After identifying that job, use:
npm run resume -- --job-id YOUR_JOB_ID
The example records a submission attempt before sending it, blocking accidental repeat submissions. To deliberately make a new video, first inspect the previous job and rename out to preserve its record. Then validate and render again. For completion states, output expiry, and failure handling, see the render lifecycle.
Turn the sample into your own acceptance checks
Change data/template.json and data/cases.json together. Start with the smallest useful input, a typical input, the largest allowed input, every important condition, and known invalid data. Keep independent cases for separate boundaries so a failure points to one clear cause.
Write expectations for the values and sequence your business needs, not just the JSON shape Zvid accepts. If an empty list should stop your application, reject it before validation; this sample deliberately allows an introduction-only video.
If you add images, footage, or audio, also inspect those sources and their availability, format, crop, and timing. Structural validation does not prove that every remote file can be downloaded or that it will remain available when a render runs.
Run the matrix after a change to data mapping, variable names, defaults, scene timing, or design. Then review the affected visuals before sending a production batch. If a check fails, the invalid-JSON troubleshooting guide helps you separate request errors from media and render-job failures.
Frequently asked questions
Does Zvid template preview create a video or use render credits?
No. POST /api/templates/{id}/preview resolves and validates a saved template, returning HTTP 200 with project and stats. It creates no render job or media and consumes no render credits. Submit a separate render to create a video.
Can I validate a Zvid template without saving it in my account?
Yes. Send the project under payload and replacement values under request-level variables to POST /api/render/validate/api-key. The supplied example uses this route, so it requires neither a template ID nor a spare saved-template slot.
Does successful validation prove that long text will fit in a video?
No. Validation can check project structure and report some layout warnings, but it does not approve the finished typography. Review long-copy states at the intended output size and render the affected states when necessary.
Why does a four-scene video last 10.5 seconds instead of 12 seconds?
The sample's three half-second fades overlap adjacent scenes. Four three-second scenes total 12 seconds, and the 1.5 seconds of overlap reduce the finished timeline to 10.5 seconds.
What should I do if my render stops at 100 percent progress?
Keep checking the saved job until its state is completed or failed. Progress is a hint, not the completion condition. In the supplied example, run npm run resume to continue tracking that job without creating another paid render.