HyperFrames templates are compositions that take typed variables — a name, a colour, a chart payload, a CTA URL — and produce a finished render parameterised by those values. Pair a template with the deployed Lambda stack andDocumentation Index
Fetch the complete documentation index at: https://hyperframes.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
lambda render-batch, and you get personalised-video-at-scale in one CLI call:
hyperframes render, deploy to Lambda once, then fan out N renders from a batch file. The same flow also drives single personalised renders via lambda render --variables and programmatic batches via renderToLambda({ variables }).
What’s a template
A template is just a HyperFrames composition whose top-level HTML element declares adata-composition-variables attribute listing the variables it accepts. The composition reads the runtime values via window.__hyperframes.getVariables().
window.__hyperframes.getVariables() — not as a fetchable module. Use a plain <script> (not <script type="module">) so the runtime is already initialized when the script executes.
Declaring variables
Each entry in thedata-composition-variables array describes one variable. Supported shapes:
| Field | Required | Example |
|---|---|---|
id | yes | "title" |
type | yes | "string", "number", "color", "boolean", "enum" |
label | recommended | "Headline" |
default | recommended | "Welcome" |
"enum"-only options field.
getVariables() returns the merged result of declared defaults and any caller overrides, so a composition with sensible defaults renders unchanged in preview mode and in production. Render-time overrides come from --variables '{...}' on the CLI or the variables field on the SDK’s renderToLambda call.
Variables are typed primitives; for structured data (a list of bullets, a nested record), serialise it on the caller side and parse it back inside the composition:
type: "object" — the parser rejects anything outside the five canonical types and silently drops the declaration, so --strict-variables would then flag every key as undeclared.
Local iteration loop
Fast iteration is the whole point of templates — you should not have to deploy to Lambda to see how a value looks. Usehyperframes render locally with --variables (or --variables-file) to render the template against any payload:
--strict-variables to fail on type mismatches against the data-composition-variables declaration. Without the flag, mismatches print as warnings and the render continues.
Deploying to Lambda
Templates render on the standardhyperframes lambda stack — there’s no special template-only deployment. Run:
lambda sites create and reference its content-addressed siteId from every subsequent render or batch:
Single personalised render
For one-off renders, pass--site-id + per-render --variables. The CLI synthesises the minimal site handle from the siteId (no re-tarring) and invokes renderToLambda:
--wait streams progress lines until the render finishes; without it the CLI returns immediately and you poll via hyperframes lambda progress <renderId>.
Batch pipeline (the headline)
lambda render-batch is the headline ergonomic: one CLI call dispatches N personalised renders. Author a JSONL file with one entry per recipient:
--site-id), then calls renderToLambda per row. Variables travel inline in each JSONL entry — render-batch does not accept --variables-file because per-entry payloads are the whole point. Concurrent Step Functions starts are capped at --max-concurrent (default 50) so a 10 000-entry batch doesn’t try to spawn 10 000 executions simultaneously and trip the AWS account’s concurrent-execution limit.
The manifest output gives one row per input line:
--json for the machine-readable form your batch coordinator can pipe to jq:
executionArn (or renderId) with lambda progress to track completions:
--dry-run to lint a batch file before paying for any executions. Every entry’s status becomes would-invoke:
Programmatic via SDK
The same surface is available from TypeScript via@hyperframes/aws-lambda/sdk. Deploy the site once and parallel-render the batch:
HYPERFRAMES_BUCKET and HYPERFRAMES_SFN_ARN come from the deployed stack. hyperframes lambda deploy prints them as RenderBucketName and RenderStateMachineArn, and they’re also available via aws cloudformation describe-stacks --query "Stacks[0].Outputs". See the aws-lambda deploy guide for the full CloudFormation outputs table.
Wrap the Promise.all in a semaphore (or use the CLI’s runWithConcurrencyLimit pattern) when the batch is large enough that an unbounded burst would trip your AWS Lambda concurrent-execution quota.
Working with large variables
Variables travel inside the Step Functions Standard execution input, which AWS caps at 256 KiB for the entire input (not just the variables — the cap is on the full serialised payload). Express workflows cap at 32 KiB; we use Standard for execution-history visibility, so 256 KiB applies. The SDK validates the size client-side and rejects oversize inputs with a clear error before any AWS call:inputProps — if you’re migrating from @remotion/lambda, your payloads should already be structured this way.
If your typed-data payload genuinely exceeds 256 KiB (e.g. a long structured record per render with no media), file an issue — there’s a clean path via S3-hosted variable files, but we want to see real demand before designing the API.
Cost and scale
Each personalised render is one Step Functions execution + N chunk Lambda invocations. At default settings (chunkSize: 240, maxParallelChunks: 16) a 5-second 30fps composition is 1 chunk; a 60-second composition is ~8 chunks.
The cost knobs:
--max-parallel-chunks: per render, default 16. Smaller compositions don’t fan out beyondceil(totalFrames / chunkSize). Higher values pay more Lambda invocations but finish faster.- Lambda reserved concurrency (
lambda deploy --concurrency=<N>): caps how many Lambda invocations the render function can run in parallel. Other workloads in the same AWS account share the same account-level concurrency pool (~1 000 in most regions by default), so reserved concurrency keeps the render function from starving them and vice-versa. render-batch --max-concurrent: orchestrator-side. Caps how manyStartExecutioncalls run simultaneously — distinct from the Lambda concurrency cap, which lives one level below at the chunk-invoke layer. The CLI cannot enforce Lambda’s account limit; it can only avoid creating excess Step Functions executions queued against it.- Lambda memory (
lambda deploy --memory): default 10 240 MB (max). Higher memory buys faster Chrome capture + more vCPUs per chunk; lower memory saves cost but risks15 mintimeouts on heavy compositions.
maxParallelChunks Lambda invocations. So if the deployed reserved concurrency is 8 and maxParallelChunks stays at the 16 default, even a single render will get throttled — bump the deploy concurrency before running large batches.
For small batches (< 100 entries) the default --max-concurrent 50 is fine. For large batches (> 1 000), a useful starting point is --max-concurrent ≈ floor(reservedConcurrency / maxParallelChunks) so each running render gets its full chunk fan-out budget; the batch verb does NOT enforce this, it’s just guidance for picking the flag value.
In-process vs distributed crossover: for a single render under ~30 seconds, the in-process renderer (hyperframes render) wins on latency because there’s no S3 round-trip per chunk. Distributed wins for renders over ~60 seconds or when you need a personalised batch — that’s the whole reason this surface exists. (The Phase 7 small-render shortcut, when it lands, will collapse the gap for short renders.)
Migrating from @remotion/lambda inputProps
Remotion’sinputProps API and HyperFrames’ variables are isomorphic — both are JSON objects injected as render-time overrides on top of declared composition defaults. The mapping is mechanical:
| Remotion | HyperFrames |
|---|---|
Composition.defaultProps | data-composition-variables declaration on the root HTML element |
useCurrentFrame() + props.<x> | window.__hyperframes.getVariables().<x> (read once on DOMContentLoaded) |
renderMediaOnLambda({ inputProps }) | renderToLambda({ config: { variables } }) |
| Lambda inputProps 256 KiB cap | Step Functions execution-input 256 KiB cap |
| inputProps URL’ing pattern for large media | Same convention — URL references, not inlined bytes |
inputProps has the same 256 KiB constraint and the same “URL your assets” convention, so a migration of a working inputProps pipeline is a straightforward CLI/SDK swap, not a payload reshape.
What’s next
- Smaller batch primitives: HTML-form input alongside JSONL. Open an issue if you’d find this useful.
- TypeScript types generated from
data-composition-variables:hyperframes types generate <projectDir>is sketched and may land in v1.5; it would let SDK callersimport type { Variables } from "./template/variables"for autocomplete + typecheck. - HDR template support: HDR mp4 is currently distributed-mode-rejected (in-process only). The next v1.5 item is unblocking HDR for distributed renders so templates can produce wide-gamut output.