Build a 6-second title card. Make the name, the logo, and the accent color variables; everything else stays fixed.Default variable values. The same composition re-rendered with
--variables overrides — different name, logo, and accent, zero re-prompting.
The agent declares data-composition-variables on the composition root, with
the right type for each slot. The name is a string. The accent is a color.
The logo is an image, and a plain URL is a valid value for it. A plain <img>
logo needs no timing attributes. Only <video> and <audio> variables involve
the media wiring described in variables. One composition,
many fills.
Say what type each slot is
There are seven variable types:string, number, color, boolean, enum,
font, and image. Each one validates differently at render time. In
Studio, boolean, enum, color, and number each get
their own control, while string, font, and image use a plain text input.
You don’t write the JSON yourself. But naming the type in the prompt removes a
guess:
Variables:plan(enum: Free / Pro / Enterprise),price(number, shown as$),featured(boolean — toggles the ribbon),headline(text).
- ❌
make the plan and price editable - ✅
plan is an enum (Free / Pro / Enterprise); price is a number in dollars
enum with declared options is checked against that
list at render time, so enum-out-of-range gets caught. A number can carry
min, max, step, and a unit label, which is what gives Studio a real
slider instead of a bare text box. Say “editable” and you leave the agent to
pick a type. A mistyped value then surfaces much later.
Template, then render one per record
Once the varying parts are variables, the same source renders once per data row. This is a real batch mode, not a copy-paste-per-video loop. You author the composition once and feed it a list of value sets:Build this as a template withThe agent authors the composition, then runs a batch render. The batch input is a JSON array. Each row is one set of variable values, and each row produces one output file.nameandtitlevariables, then render one video per row of my data — output torenders/{name}.mp4.
{key} placeholders in the output path get filled from that row.
If your source is a CSV, say so. The agent converts it to the row array the
batch expects.
Add “fail on any undeclared or mistyped value” and it renders with
--strict-variables. A typo in a column name then stops the run instead of
silently rendering the default.
Everything shares one composition. So a design fix propagates to every output on
the next render. You are not editing a hundred near-duplicate files.
Personalization asks
Personalized-at-scale videos are the same pattern, with the value set coming from your data:
A 10-second welcome clip that greets each new signup by first name and shows their company logo. I’ll supply a list of { firstName, logoUrl } records.
firstName is a string. logoUrl is the image slot your composition binds to
an <img src>.
Pass assets as URL references, not inlined data. URL-shaped values travel
cleanly through both the local renderer and distributed
Lambda renders.
Wiring this behind your own product UI or an agent instead of the CLI? The
@hyperframes/sdk opens a base template and layers a sparse
override set per instance. The host then stores only each record’s delta.
Declare up front — don’t bake values in
The most common miss is describing the finished video with the values already fixed, then asking to “make it reusable” afterward:- ❌
Make a card that says "Acme — Pro plan — $49". Later I'll want other companies too. - ✅
Make a plan card. Variables: company (text), plan (enum), price (number, $). Show "Acme / Pro / 49" as the default.
"Acme — Pro — $49" into the
markup and you get a composition with no slots. Reuse then means an edit pass
over hardcoded text for every variant. That is exactly what variables exist to
avoid.
Prove the template actually re-skins
A template that never re-skins can pass every gate you have.lint and check
verify structure. --strict-variables catches an undeclared or mistyped key.
Neither can tell you whether the values you passed ever reached the DOM.
The failure looks like success. The render completes, exits clean, and is
pixel-identical to the default.
So test it differentially. Render twice and compare:
--variables overrides are global by variable ID. The compiler
applies a matching override to CSS custom properties on the root and on
sub-compositions pulled in with data-composition-src. data-variable-values
is still the per-instance way to give two mounts different values.
Scoped JavaScript inside a sub-composition reads its own per-instance variable
table. So forward values at the mount point when that script calls
getVariables() instead of reading CSS.
The capstone keeps its variables on one root file for
simplicity, not because templates require one file. Sub-compositions work as
long as shared CSS-bound IDs are declared consistently. Use mount-point values
for instance-specific or JavaScript-read inputs. Its exact variable clause is
quoted at the bottom of this page.
What can’t be a variable
A few inputs are read once at compile time, and no variable can move them:- composition dimensions (
data-width/data-height) - the root composition’s total duration
- frame rate
- output format, codec, or quality
- ❌
make the video length a variable so each render can be a different duration - ✅
author one composition per target length— or vary a clip’s duration, which is re-read from the live DOM
data-duration
per render, not a variable. See
what can’t be a variable for the
full list and the compile-time-vs-live-DOM rule behind it.
An authored CSS custom property always wins over a same-named variable. Say
your composition already defines its own
:root { --accent: ... } as a
hand-written theme token. A variable called accent never overwrites it — the
authored value stands. A render-time --variables override still wins over
both. So when you need to override an authored value per render, use
--variables, not a same-named declared variable.Related
Variables (concept)
The mechanics: declaring types, per-instance overrides, batch renders, precedence.
@hyperframes/sdk
Template + sparse-override editing behind your own product UI or agent.
The specification dial
How much to specify — and why naming the type is cheap precision.
Design systems and brand
Brand tokens as variables that re-skin every reuse from one value.
Capstone thread — the Level 7 film is a working
template. One single-file composition, one variable scope. Its second render is
nothing but one
--variables flag: navy ground, acid-green ink, every region
re-skinned including the generated mural. Both full renders are embedded on the
capstone page.Variables: exposeNext: Storyboards — for multi-scene work, prompt the plan a frame-by-frame build fills in, not the scenes one by one.ground(default#0a0a0a) andink(default#3CE6AC) as composition variables on the single root file, bound via CSS custom properties everywhere (including the duotoned mural), so one--variablescall re-skins the entire journey. It will be rendered twice: the default brand palette, and a second full render with{"ground":"#0d1420","ink":"#c8ff3d"}. Architecture constraint (technical): single composition file — oneindex.html, one variable scope. […] Nodata-composition-srcsub-files.