Template JSON reference
Every field of a template's pages_data, with types, limits and renderer rules, for creating or updating templates through the API or an AI agent.
Updated
A template's pages_data is an array of pages. Each page has a canvas, layers (elements) and, for video, audio tracks and subtitles. A film also has a template-level film object, sent next to pages_data; see Film. This page lists the fields, then the rules the renderer follows. For how the pieces fit together, start with Template anatomy.
Field reference#
Page#
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Page id. Stays the same when pages are added, moved or removed. | |
name | string | Page name shown in the editor. | |
canvas | object | Page size and background. See Canvas. | |
elements | object[] | The page's layers. See Element. | |
thumbnail_url | string | Preview image of the page. Read only: set by Orshot. | |
audioTracks | object[] | null | The page soundtrack, mixed into mp4 and webm renders. Each track has one source: an existing url, generated narration (tts) or a generated sound (sfx). A render is refused when a page has more tracks than the plan allows per page. See Audio track. | |
subtitle | object | null | Burned-in captions for video renders. source "auto-audio" captions the first enabled audio track that is not a sound effect. Render-time videoOptions subtitle* keys override these. See Subtitle. | |
videoDuration | number | null | Saved video length of this page in seconds. A render's videoOptions.duration overrides it. In a film it is the scene's length. | |
transitionIn | object | null | Films only: how this page starts from the previous one. Leave it out (or null) for a hard cut; page 1 has none. See Page transition and Film. | |
motion | object | Page motion: look, camera and groups. Any motion on a page switches it to the motion timeline. See Motion. |
Canvas#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
width | number | Yes | min 1, max 5000 | Canvas width in pixels. |
height | number | Yes | min 1, max 5000 | Canvas height in pixels. |
backgroundColor | string | Page background: a color or a CSS gradient. | ||
backgroundImage | string | Background image URL. | ||
borderWidth | number | Page border width in pixels. | ||
borderColor | string | Page border color. | ||
borderStyle | string | Page border style, such as solid or dashed. |
Element#
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Layer id, unique among the layers of its page. A short readable string such as "headline" is fine. Generated when left out; keep it when you send the design again so references to it stay valid. | |
type | "text" | "image" | "element" | "shape" | "video" | "waveform" | Yes | Layer type. element is an SVG layer (with isSvg: true); waveform draws a page audio track. |
videoOptions | object | [video layers] Playback options for the clip: loop, muted (default true), trim_start_time and trim_end_time, duration. See Video layer options. | |
waveTrack | integer | [waveform elements only] Zero-based page audioTracks index to visualize (default 0) | |
shapeType | "rectangle" | "circle" | "arrow" | "line" | Shape kind, for type shape. | |
name | string | Layer name shown in the editor. | |
content | string | Text for text layers; a URL for image and video layers; a data:image/svg+xml URI or an .svg URL for SVG layers. | |
position | object | Yes | { x, y } in pixels from the page's top-left corner. Create and update also accept flat x and y. |
dimensions | object | Yes | { width, height } in pixels. Create and update also accept flat width and height. |
style | object | Style keys. See Element style. | |
zIndex | number | Stacking order. Higher draws on top. | |
rotation | number | Rotation in degrees. | |
parameterizable | boolean | true makes the layer a dynamic parameter that renders can change. | |
parameterId | string | The key renders use to change this layer. Letters, digits and underscores; no spaces, @, . or :. Layers of the same type on a page may share an id. On a multi-page template the render key gets a page prefix, such as page2@headline; do not put the prefix in parameterId. | |
parameterType | string | What the parameter changes: text, imageUrl, videoUrl, fill, color, stroke or backgroundColor. It defaults from the layer type. | |
isSvg | boolean | true for SVG layers (type element). | |
motion | object | Motion for video pages: reveal, typewriter, count, wake, after, pulse, float, press, sleep, drawIn, svg and keyframes. Any motion switches the page to the motion timeline; static renders ignore it. See Motion. | |
transitions | object | Enter, exit and ambient animation plus the showAt and hideAt visibility window, for video and GIF renders. showAt and hideAt sit next to enter and exit, not inside them. Static renders show the final state. See Transitions. |
Element style#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
backgroundColor | string | [image/element] Background color or gradient behind the layer. Text ignores it and takes textBackgroundColor; shapes ignore it and paint fill (black when fill is missing). | ||
backgroundImage | string | [all] Background image URL or CSS gradient | ||
backgroundSize | string | [all] "cover" | "contain" | "100% 100%": how background image fills the element | ||
backgroundPosition | string | [all] "center" | "top left" etc.: background image position | ||
borderRadius | string | [all] "0px", "12px", "50%": corner rounding | ||
borderWidth | string | [all] "0px", "2px": border thickness | ||
borderColor | string | [all] Border color as hex or rgba | ||
boxShadowX | string | [all] "0px": horizontal shadow offset | ||
boxShadowY | string | [all] "4px": vertical shadow offset | ||
boxShadowBlur | string | [all] "8px": shadow blur radius | ||
boxShadowColor | string | [all] "rgba(0,0,0,0.2)": shadow color | ||
mixBlendMode | string | [all] "normal" | "multiply" | "screen" | "overlay" etc. | ||
filter | string | [all] CSS filter string e.g. "blur(5px)", "grayscale(100%)" | ||
dropShadowX | string | [all] "0px": drop shadow horizontal offset (CSS filter) | ||
dropShadowY | string | [all] "0px": drop shadow vertical offset | ||
dropShadowBlur | string | [all] "0px": drop shadow blur radius | ||
dropShadowColor | string | [all] "rgba(0,0,0,0.2)": drop shadow color | ||
textMode | "overflow" | "truncate" | "fit" | overflow (default) lets text run past the box, truncate cuts it with an ellipsis, fit shrinks it down to minFontSize. A fourth mode, "flow", splits long text across extra pages; see Flowing content. | ||
minFontSize | string | [text only] Minimum font size when textMode is "fit": string like "8" | ||
fontFamily | string | [text only] Google Font name e.g. "Inter", "Prata", "DM Sans", "Playfair Display" | ||
fontSize | string | [text only] Font size with px e.g. "48px", "72px" | ||
fontWeight | string | [text only] "400", "700", "900" | ||
fontStyle | string | [text only] "normal" | "italic" | ||
color | string | [text, line and arrow] Text or line color: hex or gradient. | ||
textAlign | string | [text only] "left" | "center" | "right" | "justify" | ||
verticalAlign | string | [text only] "flex-start" (top) | "center" (middle) | "flex-end" (bottom) | ||
lineHeight | number | [text only] Line height multiplier e.g. 1.2, 1.4 | ||
letterSpacing | string | [text only] "0px", "2px": letter spacing | ||
textTransform | string | [text only] "none" | "uppercase" | "lowercase" | "capitalize" | ||
textDecoration | string | [text only] "none" | "underline" | "line-through" | ||
paddingX | string | [text only] Horizontal space between the text and its background highlight (textBackgroundColor), default "6px". It does not move the text. | ||
paddingY | string | [text only] Vertical space between the text and its background highlight, default "6px". It does not move the text. | ||
textStrokeWidth | string | [text only] "0px", "2px": text outline/stroke width | ||
textStrokeColor | string | [text only] "#000000": text outline/stroke color | ||
textBackgroundColor | string | [text only] The text box's background colour (a label, pill or highlight box). style.backgroundColor does nothing on text. | ||
textBackgroundRadius | string | [text only] "0px", "4px": border radius of text background highlight | ||
textBorderWidth | string | [text only] "0px", "2px": border around the text element | ||
textBorderColor | string | [text only] Border color for text element | ||
fill | string | [shape/waveform] Shape paint or waveform bar color. For shapes, backgroundColor does nothing: use a color, rgba, or gradient such as "rgba(0,0,0,0.5)" or "linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.9) 100%)". | ||
stroke | string | [shape only] Stroke color (rectangle/circle shapes) | ||
strokeWidth | string | [shape only] "0px", "2px": stroke width | ||
lineStyle | string | [shape only] "solid" | "dashed" | "dotted": for line/arrow shapeType | ||
objectFit | string | [image only] "contain" | "cover" | "fill": how image fills the element | ||
objectPosition | string | [image only] "center" | "left top" | "right bottom" etc. | ||
clipPath | string | [image only] CSS clip-path for masking images | ||
svgColor | string | [SVG only] Hex color to tint SVG elements (type: element with isSvg: true) | ||
barCount | integer | min 4, max 160 | [waveform only] Number of bars, 4-160 (default 48) | |
barGap | number | min 0, max 24 | [waveform only] Gap between bars in px, 0-24 (default 3) | |
waveAlign | "center" | "bottom" | [waveform only] "center" grows bars from the middle; "bottom" grows them from the baseline | ||
minBarHeight | number | min 0, max 60 | [waveform only] Quiet/minimum bar height as a percentage, 0-60 (default 8) |
Transitions#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
enter | object | Entrance animation | ||
exit | object | Exit animation | ||
ambient | object | Ambient motion that plays for the layer's whole visible life, starting when its enter finishes. It combines with enter and exit. See Ambient transition. | ||
showAt | number | min 0 | Seconds from the start when the layer appears and its enter plays. A sibling of enter on transitions, not inside enter. | |
hideAt | number | null | min 0 | Seconds from the start when the exit starts; the layer is gone at hideAt + exit.duration. null or left out keeps it to the end (a video layer stays until its trimmed clip ends). A sibling of exit, not inside it. |
Enter transition#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
type | "none" | "fadeIn" | "slideInLeft" | "slideInRight" | "slideInUp" | "slideInDown" | "scaleIn" | "scaleInUp" | "scaleInDown" | "rotateIn" | "flipInX" | "flipInY" | "textReveal" | "typewriter" | Yes | Enter animation. textReveal and typewriter are staggered per word or character and work on text layers only. flipInX and flipInY are 3D flips. | |
duration | number | min 0 | Duration in seconds (default: 0.5) | |
easing | "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "cubic-bezier(0.175, 0.885, 0.32, 1.275)" | "spring(180, 12, 0)" | "spring(stiffness, damping, velocity)" | Easing (default "ease-out"). "cubic-bezier(0.175, 0.885, 0.32, 1.275)" = classic bounce; "spring(stiffness, damping, velocity)" = physics spring, e.g. "spring(180, 12, 0)" (stiffness 20-400, damping 2-40, velocity -20..20). | ||
reverse | boolean | Play the animation direction reversed | ||
customValues | object | Override the preset's intensity instead of using the fixed default. Per-preset intensity knobs (all optional, defaults in parens): - opacity: start/end opacity 0-1 (0): all fade/slide/scale/rotate/flip presets - offset: slide distance as % of element size, -500..500 (±100; scaleInUp/Down ±50; textReveal rise 100): sign sets direction - scale: start/end scale 0-5 (scaleIn 0: set 1.15 for a subtle zoom-punch to 1.0; scaleInUp/Down 0.5) - angle: rotation degrees (rotateIn -180, rotateOut 180, range ±720; flipInX/Y ±90, range ±180) - granularity: "word" | "char": textReveal/typewriter split unit (STRING value) - overlap: 10-90: textReveal per-item duration % of total (40); lower = tighter stagger |
Exit transition#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
type | "none" | "fadeOut" | "slideOutLeft" | "slideOutRight" | "slideOutUp" | "slideOutDown" | "scaleOut" | "scaleOutUp" | "scaleOutDown" | "rotateOut" | "flipOutX" | "flipOutY" | "textRevealOut" | Yes | Exit animation; it starts at hideAt. textRevealOut works on text layers only. | |
duration | number | min 0 | Duration in seconds (default: 0.5) | |
easing | "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "cubic-bezier(0.175, 0.885, 0.32, 1.275)" | "spring(180, 12, 0)" | "spring(stiffness, damping, velocity)" | Easing (default "ease-in"). Named value, bounce cubic-bezier, or "spring(stiffness, damping, velocity)". | ||
reverse | boolean | Play the animation direction reversed | ||
customValues | object | Override the preset's END state intensity. Per-preset intensity knobs (all optional, defaults in parens): - opacity: start/end opacity 0-1 (0): all fade/slide/scale/rotate/flip presets - offset: slide distance as % of element size, -500..500 (±100; scaleInUp/Down ±50; textReveal rise 100): sign sets direction - scale: start/end scale 0-5 (scaleIn 0: set 1.15 for a subtle zoom-punch to 1.0; scaleInUp/Down 0.5) - angle: rotation degrees (rotateIn -180, rotateOut 180, range ±720; flipInX/Y ±90, range ±180) - granularity: "word" | "char": textReveal/typewriter split unit (STRING value) - overlap: 10-90: textReveal per-item duration % of total (40); lower = tighter stagger |
Ambient transition#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
type | "none" | "kenBurns" | "pulse" | "float" | "drift" | "wiggle" | "shake" | "slowRotate" | "breathe" | Yes | Ambient animation. Loop types repeat every duration: pulse, float, drift, wiggle, shake, slowRotate, breathe. kenBurns runs once across the layer's visible window and ignores duration. | |
duration | number | min 0 | Loop length in seconds (default 4). kenBurns ignores it and spans the layer's visible window. | |
easing | "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "cubic-bezier(0.175, 0.885, 0.32, 1.275)" | "spring(180, 12, 0)" | "spring(stiffness, damping, velocity)" | Easing. Defaults to "linear" for shake and slowRotate (a seamless loop needs linear) and "ease-in-out" for the rest. | ||
customValues | object | Override the type's intensity. Per-type intensity knobs (all optional, defaults in parens): - kenBurns: zoom = end scale 0.5-2 (1.12), panX / panY = drift across the frame in %, -30..30 (0) - pulse: amount = peak scale 1-1.5 (1.04) - float: offset = vertical travel in px, 1-100 (12) - drift: offset = circular radius in px, 1-100 (10) - wiggle: angle = sway in degrees, 0.5-30 (2) - shake: offset = horizontal jitter in px, 1-40 (4) - breathe: opacity = dimmest point 0.1-1 (0.85), scale = peak scale 1-1.3 (1.02) - slowRotate: no knobs: use duration for speed |
Video layer options#
| Field | Type | Required | Description |
|---|---|---|---|
loop | boolean | Replay the clip when it ends (default false) | |
muted | boolean | Mute the clip's own audio. Default true; set false to keep the clip's sound, which mixes with the page audio tracks. | |
trim_start_time | number | string | Clip trim start: seconds (4.5) or "MM:SS". Applies only with trim_end_time, and only to the template's own clip. At render time use the camelCase key "paramId.trimStart". | |
trim_end_time | number | string | Clip trim end: seconds or "MM:SS". Applies only with trim_start_time. At render time use "paramId.trimEnd". | |
duration | number | null | The source clip's own length in seconds. Set it when known; page length is worked out from it. |
Audio track#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
id | string | Track id (uuid; the editor assigns one if missing) | ||
url | string | null | Existing public audio URL, such as a brand-audio direct_url. Omit when authoring a new tts or sfx source; resolved generated tracks may later contain both their recipe and cached URL. | ||
tts | object | Generated narration. Orshot synthesizes and caches it when the template is saved or rendered. See Narration (tts). | ||
sfx | object | Generated sound or ambient bed, not speech. Orshot synthesizes and caches it when the template is saved or rendered. See Sound effect (sfx). | ||
filename | string | null | Display filename | ||
label | string | Display label, e.g. "Audio 1" | ||
duration | number | null | Source file duration in seconds: set it when known (URL-added audio can't be probed automatically) | ||
volume | number | min 0 | 0-1+, default 1. 0 disables the track. | |
fadeIn | number | min 0 | Fade-in length in seconds (default 0) | |
fadeOut | number | min 0 | Fade-out length in seconds (default 0) | |
trimStart | number | min 0 | Start inside the SOURCE file, seconds (default 0): use with trimEnd to bundle only the "best part" of a track | |
trimEnd | number | null | min 0 | End inside the source file, seconds (null = play to the end) | |
offset | number | min 0 | Placement on the VIDEO timeline, seconds from the start (default 0) | |
loop | boolean | Loop the (trimmed/generated) track to fill the video. Usually true for ambient SFX and false for narration. | ||
muted | boolean | Mute this track (default false) | ||
enabled | boolean | Track on/off (default true) |
Narration (tts)#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
text | string | Yes | Narration script. Up to 40,000 characters with flash, 10,000 with multilingual and 5,000 with expressive; your plan may set a lower cap. Keep it short enough for the page's videoDuration. | |
voiceId | string | min length 1 | Voice id. Leave it out to use the default voice. | |
modelId | "flash" | "expressive" | "multilingual" | Speech model: flash (fast, the default), expressive or multilingual. Premium models depend on the plan. | ||
stability | number | min 0, max 1 | Delivery consistency, 0-1. Leave it out for the voice default. | |
similarity | number | min 0, max 1 | Voice similarity, 0-1. Leave it out for the voice default. | |
style | number | min 0, max 1 | Expressive style strength, 0-1. Leave it out for the voice default. | |
speakerBoost | boolean | Speaker clarity boost. Leave it out for the voice default. | ||
parameterId | string (no spaces, @, . or :) | Makes the spoken script a render parameter. With "voiceover", a render sends modifications: { "voiceover": "New script" }; on a multi-page template the render key is "page1@voiceover". |
Sound effect (sfx)#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
prompt | string | Yes | min length 1, max length 450 | Describe a non-verbal sound or ambient bed, e.g. "warm modern technology pulse, instrumental, no vocals, seamless loop". |
seconds | integer | min 1, max 22 | Generated source length in seconds (1-22, default 12). Set loop:true on the track when it should fill a longer page. |
Subtitle#
| Field | Type | Required | Description |
|---|---|---|---|
url | string | null | Subtitle .srt/.vtt URL, or a media URL to transcribe (when source is "upload") | |
filename | string | null | Display filename. | |
source | "upload" | "auto-audio" | "auto-audio" captions the page's first enabled audio track that is not a sound effect (narration reuses its word timing; other audio is transcribed at render). "upload" uses url. | |
enabled | boolean | default true | |
timingOffset | number | null | Seconds added to every cue (+ = captions later). Nudge for transcription timing drift on dense music. | |
mode | "phrase" | "word-by-word" | null | "word-by-word" = karaoke-style single highlighted word | |
maxWordsPerLine | integer | null | Words per caption line. | |
maxCharsPerLine | integer | null | Character budget per caption line. When set, it overrides maxWordsPerLine; 1 creates a one-word-at-a-time look. | |
fontSize | string | number | null | e.g. "30px" (numbers get px appended); null = renderer default | |
fontFamily | string | null | Brand fonts work by family name | |
fontWeight | string | number | null | Caption font weight. | |
color | string | null | Caption text color. | |
background | string | null | Highlight-block background, e.g. "rgba(0,0,0,0.6)" | |
position | "top" | "center" | "bottom" | null | Where captions sit on the frame. | |
offset | string | number | null | Distance from the top or bottom edge. Ignored when position is center. | |
paddingX | string | number | null | Horizontal padding inside the caption background. | |
paddingY | string | number | null | Vertical padding inside the caption background. | |
borderRadius | string | number | null | Corner rounding of the caption background. | |
shadowX | number | null | Caption text shadow, horizontal offset. | |
shadowY | number | null | Caption text shadow, vertical offset. | |
shadowBlur | number | null | Caption text shadow blur. | |
shadowColor | string | null | Caption text shadow color. | |
wordSpacing | string | number | null | Space between caption words. | |
preset | "pill" | "minimal" | "karaoke" | "bold" | "outline" | "neon" | null | Set by Studio: the caption preset that was applied. It does not apply a look on its own; the styling keys do. | |
wordReveal | "appear" | "highlight" | "align" | null | word-by-word only. "highlight" dims the full line and lights up the current word; "appear" pops each word in; "align" reveals spoken words and re-centers the growing line. | |
split | "word" | "letter" | null | Reveal granularity for word-by-word captions. "letter" reveals each letter; default "word". | |
activeColor | string | null | Color of the current word in word-by-word mode: the karaoke highlight. | |
activeBackground | string | null | Background behind the current word in word-by-word mode. | |
textTransform | string | null | "uppercase" | "lowercase" | "capitalize" | |
strokeWidth | number | null | Caption outline thickness in px (default 0). | |
strokeColor | string | null | Outline color (default "#000000") |
Page transition (transitionIn)#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
type | "none" | "fade" | "slide" | "push" | "wipe" | "iris" | "clockWipe" | "flip" | "cube" | "whip" | "blurSwap" | "zoom" | "colorFlood" | "dip" | "grow" | "match" | "zoomThrough" | How this page starts from the previous one. Overlapping: fade, dip, slide, push, wipe, iris, clockWipe, flip, cube, whip, blurSwap, zoom, colorFlood. Continuity (a layer of the previous page carries the cut): grow (+from), zoomThrough (+target), match (+pairs). "none" = hard cut, used with overlay. Omit transitionIn for a plain cut. | ||
duration | number | min 0, max 3 | Seconds, 0-3. Defaults per type: whip 0.6; fade, slide, push and wipe 0.9; dip, iris, blurSwap, zoom, clockWipe, flip, cube and zoomThrough 1.0; colorFlood and match 1.1; grow 1.2. Must stay under half of the shorter neighbouring scene. Both scenes play during it, so the film is shorter by this amount. | |
ease | string | min length 1, max length 120 | Ease names such as power2.inOut, expo.out, sine.in or back.out, "none" or "linear", or cubic-bezier(). Unknown names play as power2.inOut. | |
direction | "left" | "right" | "up" | "down" | For slide, push, wipe, flip, cube, whip. Default left (the new page comes from the right). | ||
color | string (CSS color) | min length 1, max length 100 | For dip (default "#000000") and colorFlood (default "#111111"): the colour the cut passes through. | |
origin | object | For iris, clockWipe, zoom and colorFlood: the point the effect opens from, { x, y } as fractions of the frame (default 0.5, 0.5). | ||
from | string | min length 1, max length 200 | grow only, REQUIRED: the id of the layer on the PREVIOUS page that grows to fill the frame and becomes this page (a card, a stamp, a phone). Falls back to zoom if the layer is gone. | |
target | string | min length 1, max length 200 | zoomThrough only, REQUIRED: the id of the layer on the PREVIOUS page the camera dives through (a door, a screen, a window) into this page. | |
pairs | object[] | max 24 items | match only, up to 24: each pair moves from its place on the previous page to its place on this page. Default: layers that have the SAME id on both pages. A paired layer on this page must not have its own entrance (wake, reveal, typewriter, transitions.enter); it arrives through the morph. | |
others | "clear" | "dissolve" | match only: what the unpaired layers do. "clear" (default) fades the old ones out first and the new ones in last; "dissolve" crossfades everything. | ||
reveal | "iris" | "fade" | match only: how this page appears around the morph. "iris" (default) opens a circle from behind the paired layer; "fade" crossfades. | ||
overlay | object | An effect laid over a HARD cut: { "type": "none", "overlay": { "type": "flash" } }. Never together with a transition type; never changes timing. |
Film (template level)#
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
v | 1 | Yes | Always 1. | |
background | string (CSS color) | min length 1, max length 100 | CSS colour shown under the scenes while a transition reveals part of the frame (slide, push, zoom, grow). Default: page 1's canvas backgroundColor, else #000000. Set it to the film's base colour. | |
output | object | Encoding: { profile: "default" | "film", fps: 24 | 25 | 30 }. "film" is the higher quality tier and usually a smaller file. | ||
tracks | object[] | max 8 items | Up to 8 tracks that run across scenes: audio (music, ambience) and layers (a logo, a progress bar, a background video under the cuts). Page audioTracks still play inside each scene. See Film. |
Elements#
Note. Each element: { id, type, position: { x, y }, dimensions: { width, height }, style: { ... }, zIndex, content }. position and dimensions are nested objects. Send style lengths as px strings ("48px"), fontWeight and minFontSize as unitless strings ("700", "8"), and lineHeight as a number multiplier (1.2).
Types
- text
- image
- shape (shapeType: rectangle|circle|arrow|line)
- element (inline SVG, isSvg:true)
- video
- waveform (visualizes a page audio track)
Parameterizable. Set parameterizable: true, a parameterId and a parameterType (text, imageUrl, videoUrl, backgroundColor, fill, color or stroke). A parameterId uses letters, digits and underscores, such as "headline"; it cannot contain spaces, @, . or :. Layers of the same type on a page may share a parameterId, so one value fills them all; a clash with a layer of another type is renamed with _1, _2. On a multi-page template the render key gets the page prefix, such as page2@headline. The prefix goes on the render key only, never in parameterId.
Opacity. Layer opacity is style.opacity, from 0 to 1.
Flat fields. Create and update also accept flat x, y, width and height on a layer (saved as position and dimensions) and src or src_url (saved as content).
Text#
Line breaks. In plain text use \n for line breaks; <br> also works. Inside rich text (content with tags such as <span> or <b>) \n is not converted, so use <br> there. Use a single <p> or none: several <p> blocks overlap.
Fit. style.textMode: "fit" with style.minFontSize (such as "8") shrinks the text to fit its box when a render sends longer copy.
Align. style.verticalAlign places text in its box: "flex-start" (top), "center" or "flex-end" (bottom).
Shapes#
Outline. A rectangle or circle paints its fill, which defaults to black (#000000). For an outline, set fill to "transparent" and set stroke and strokeWidth. Lines and arrows are drawn with color and strokeWidth.
SVG layers#
Content. type: "element" with isSvg: true. content is an inline data:image/svg+xml URI or the https URL of an .svg file. Percent-encode an inline SVG (encodeURIComponent): a raw # or < cuts the URI short and the layer renders blank. style.svgColor tints a single-colour SVG.
Parts. To animate parts of a drawing, give each moving part an id and target it with motion.svg (see Motion). Keep static placement on an outer <g> and put the id on the inner one.
Waveforms#
Structure. type:"waveform", waveTrack:0 (zero-based page audioTracks index), with normal position/dimensions/zIndex. It needs no content.
Style. style:{ fill, barCount:4-160, barGap:0-24, borderRadius, waveAlign:'center'|'bottom', minBarHeight:0-60 }. The image preview draws a deterministic static waveform; video renders animate it from the selected track.
Dynamic. A parameterized waveform can expose its flat style keys through normal dot modifications such as 'wave1.fill' or 'wave1.barCount'.
Animation#
When. Transitions play in video (mp4, webm) and GIF renders. Static renders show every layer in its final state.
Example
{
"transitions": {
"enter": {
"type": "slideInUp",
"easing": "ease-out",
"duration": 0.6
},
"showAt": 1.2,
"ambient": {
"type": "kenBurns",
"customValues": {
"zoom": 1.12,
"panX": 8
}
},
"exit": {
"type": "fadeOut",
"duration": 0.3
},
"hideAt": 3.6
}
}Visibility windows. showAt and hideAt give each layer a visibility window, in seconds. The exit animation starts at hideAt, so the layer is gone at hideAt + exit.duration. Leave hideAt out (or null) to keep the layer to the end. For a slideshow, stack full-canvas photos and give photo K showAt = cut K and hideAt = cut K+1.
Enter types
- fadeIn
- slideInUp
- slideInDown
- slideInLeft
- slideInRight
- scaleIn
- scaleInUp
- scaleInDown
- rotateIn
- flipInX (3D)
- flipInY (3D)
- textReveal (text only, staggered per word/char)
- typewriter (text only)
- none
Exit types
- fadeOut
- slideOutUp
- slideOutDown
- slideOutLeft
- slideOutRight
- scaleOut
- scaleOutUp
- scaleOutDown
- rotateOut
- flipOutX
- flipOutY
- textRevealOut
- none
Easings
- ease-out (the enter default)
- ease-in (the exit default)
- linear, ease, ease-in-out
- cubic-bezier(0.175, 0.885, 0.32, 1.275) (a bounce)
- spring(stiffness, damping, velocity), such as spring(180, 12, 0): stiffness 20-400, damping 2-40
Custom values. Per-phase intensity via enter/exit.customValues: opacity (start 0-1), offset (slide % ±500, sign = direction), scale (0-5; scaleIn default 0: use { scale: 1.15 } for a subtle zoom-punch to 1.0), angle (deg; rotateIn -180, flips ±90), granularity ('word'|'char': textReveal/typewriter), overlap (10-90 stagger % for textReveal). reverse: true flips a phase's direction.
Ambient. A third phase beside enter and exit: transitions.ambient = { type, duration, easing, customValues } plays for the layer's whole visible life, starting when its enter finishes. kenBurns runs once across the visible window and ignores duration (customValues: zoom 0.5-2, default 1.12; panX and panY ±30%). The loop types repeat every duration seconds (default 4): pulse (amount 1-1.5), float (offset px), drift (offset px), wiggle (angle deg), shake (offset px), slowRotate, and breathe (opacity and scale). shake and slowRotate loop without a visible seam only with easing "linear".
More motion. Transitions have no mid-life motion beyond the ambient types. For more, use element motion (see Motion), which also takes explicit keyframes.
Motion#
Overview. Per-element animation for video pages, beyond transitions. Any element.motion or page.motion switches the page to the motion timeline; transitions on other layers keep working in the same timeline. Static renders ignore motion. All keys are optional unless marked required; out-of-range values are clamped.
Element
| Name | Value |
|---|---|
reveal | { by: "word" | "char" (default word), stagger: 0.07 (0.01-1 s between items), duration: 0.7, dy: 120 (rise in px), style: "rise" (default) | "fade" | "blur" | "scale" | "mask", ease }. Staggered text entrance. Text layers only, up to 400 words. |
typewriter | { duration: 1.6, delay: 0 }. The text types itself. Plain-text layers only. |
count | { to (required number), from: 0, duration: 1.4, decimals: 0 (0-4), prefix, suffix, ease (default power1.out; "none" counts at a steady pace), separator (true for "485,000", or "," "." " " "'"), point ("." or ",") }. The number counts up in place. |
wake | { duration: 0.6, from: "up" | "down" | "left" | "right" | "none" (default up), distance: 34 (px), offset: 0 (s after showAt) }. A directional entrance. |
after | { element (required layer id), offset: 0 (s, may be negative) }. Starts this layer when the named layer finishes its entrance. A missing id or a cycle of after references is rejected when you save. |
pulse | { from: 0.97, to: 1.03, rate: 1.6 (cycles per s), axis: "y" | "both" (default y) }. A looping scale. |
float | { amp: 6 (px, up to 200), rate: 2.4 }. A looping vertical drift. |
press | { at (required s), dip: 6 (px, up to 60) }. One press dip, like a button being clicked. |
sleep | { at (required s), duration: 0.45 }. The layer leaves early. |
drawIn | { duration: 0.9, delay: 0, hidden: false }. SVG strokes draw themselves (SVG layers, up to 300 nodes drawn). hidden: true keeps a stroke-only shape invisible until its draw starts. |
svg | { animate: true, targets: [...] } (up to 20 targets). Animates parts inside an SVG layer. Each target: selector (a CSS selector, up to 120 characters), drawIn, spin: { rate: 8 (degrees per s) }, pulse, origin (transform origin such as "50% 100%"), keyframes (up to 40: at, x and y in viewBox units, scale, scaleX, scaleY, rotate, opacity, ease), clock: "page" (default) | "layer" (from the layer's showAt) | "entered" (from the end of its entrance), repeat (1-100 extra plays, or -1 until the layer hides), repeatDelay (s). The SVG may be up to 512 KB and 1,500 nodes; it is sanitized, and animations inside the markup are removed. Part motion is clipped to the viewBox. |
keyframes | [{ at (required s), x, y (px offsets), scale: 1, rotate (deg), opacity: 1, ease, scaleX, scaleY, blur (px, 0-200), skewX, skewY (deg, ±89), clip: { top, right, bottom, left } (% inset), color (text), fill (shape), z (stacking order from this keyframe on) }] (up to 40). An explicit track. The first keyframe holds until its at; a channel a keyframe leaves out returns to rest, so repeat a value to hold it. ease takes names such as "expo.out", "back.out(1.7)" or "none", CSS keywords or cubic-bezier(); the default is power1.inOut. |
Page
| Name | Value |
|---|---|
look | { grain: 0-0.2, vignette: 0-0.6 }. Film grain and edge darkening over the whole page. |
camera | { moves: [{ at (required s), duration (required s), x, y (pan in px), zoom: 1, ease: "ease-in-out" }] } (up to 20 moves). Moves the whole canvas. |
groups | { "<groupId>": { keyframes | wake | float | pulse | press | sleep | after } }. Moves every layer whose groupId matches as one piece, about their joint centre. A group id must not equal a layer id. Members keep their own entrances and text effects. |
Validation. Create and update reject structural problems with a 400 that lists each one: a missing or cyclic after reference, a non-numeric count.to, keyframes that are not an array, more than 200 animated layers or 2,000 tweens on a page, or a page over 120 s. Unknown keys never fail: they are ignored at render and listed in the response warnings with a suggested spelling. Renders never fail on motion; at worst the page falls back to its transitions.
Limits. reveal and typewriter work on text layers only, and typewriter needs plain text. Caps: 200 animated layers per page, 400 reveal words, 512 KB of SVG.
Page audio#
Overview. Video templates carry their own soundtrack: each page has audioTracks (an array) mixed into every mp4 and webm render. A track's source is an existing url, generated narration (tts) or a generated sound (sfx). audioTracks is a page field, next to elements and canvas, not a layer. A render is refused when a page has more tracks than the plan allows per page.
Track fields
| Name | Value |
|---|---|
url | existing public audio URL (e.g. brand audio direct_url) |
tts | { text, voiceId?, modelId?: flash|expressive|multilingual, stability?, similarity?, style?, speakerBoost?, parameterId? } |
sfx | { prompt (max 450 chars), seconds? (1-22, default 12) } |
trimStart | start inside the SOURCE file, s |
trimEnd | end inside source, s (null = to the end) |
fadeIn | s |
fadeOut | s |
volume | 0-1+ (narration usually 1; ambient usually 0.08-0.18) |
offset | placement on VIDEO timeline, s |
loop | usually true for ambient and false for narration |
muted | false |
enabled | true |
duration | source duration s: set when known for URL audio |
Example
{
"audioTracks": [
{
"label": "Narration",
"tts": {
"text": "Meet the campaign built for you.",
"voiceId": "<catalog voice id>",
"modelId": "flash",
"parameterId": "voiceover"
},
"volume": 1
},
{
"label": "Ambient",
"sfx": {
"prompt": "warm modern technology pulse, instrumental, no vocals, seamless loop",
"seconds": 12
},
"volume": 0.12,
"loop": true,
"fadeIn": 0.5,
"fadeOut": 1
}
]
}Dynamic narration. tts.parameterId makes the spoken text a render parameter. Single page: modifications: { "voiceover": "New script" }; multi-page: { "page1@voiceover": "..." }. The render synthesizes, caches and bills the new script.
Render swap. videoOptions.audioSource replaces an existing URL track at render time while keeping saved trim/fade/volume/offset/loop. It is for soundtrack-file swaps; generated narration uses tts.parameterId instead. There is no generic audio parameterType.
Mixing. Page tracks auto-mix with unmuted video-element audio. videoOptions.muted: true strips all audio from a render.
Subtitles#
Saved. page.subtitle (a page field): { source: "auto-audio" | "upload", url, enabled, mode: "phrase" | "word-by-word", maxWordsPerLine or maxCharsPerLine, wordReveal: "appear" | "highlight" | "align", split: "word" | "letter", fontSize, fontFamily, fontWeight, color, background, position: "top" | "center" | "bottom", offset, paddingX, paddingY, borderRadius, shadowX, shadowY, shadowBlur, shadowColor, wordSpacing }. maxCharsPerLine overrides the word budget; align re-centres the line as spoken words appear. source "auto-audio" captions the page's first enabled audio track that is not a sound effect.
Render time. videoOptions.subtitleSource on a render (a media URL is transcribed automatically; an .srt or .vtt file is used as it is; the array form targets pages and takes trimStart and offset) and the subtitle* keys override the saved config: subtitleWordReveal appear | highlight | align, subtitleSplit word | letter, subtitleMaxWordsPerLine or subtitleMaxCharsPerLine. For captions that follow the audio, save the audio in audioTracks and set the subtitle source to "auto-audio". Do not use the output trimStart and trimEnd to align captions: the caption and video clocks drift apart.
Video layers#
Options. element.videoOptions: { loop, muted (default true; set false to keep the clip's sound), trim_start_time, trim_end_time (snake_case; seconds or "MM:SS"), duration (the clip's own length in s) }. The saved trim applies only when both trim_start_time and trim_end_time are set, and only to the template's own clip; when a render swaps the clip URL, trim it with the render-time keys below.
Render time. Per render: 'clipParam.trimStart': 4.5, 'clipParam.trimEnd': 12, 'clipParam.muted': false, 'clipParam.loop': true (camelCase at the modification layer).
Video render options#
Options. videoOptions on a render request: duration, fps (up to 30), trimStart and trimEnd (together), quality, muted, audioSource, subtitleSource with the subtitle* styling keys, subtitleMode, subtitleWordReveal: appear | highlight | align, subtitleSplit: word | letter, subtitleMaxWordsPerLine or subtitleMaxCharsPerLine, combinePages, pageTransition with its duration, and captionPreset: pill | minimal | karaoke | bold | outline | neon.
Multi page. Multi-scene videos are films: one video whose pages are scenes joined by each page's transitionIn, with music and layers that stay across cuts on film tracks. See Film. combinePages: true still joins the pages of a plain multi-page template, and a string audioSource lays one soundtrack across it.
Canvas sizes#
Note. Suggested canvas sizes in pixels. A canvas side can be up to 5,000 px.
Common
| Name | Value |
|---|---|
ig-post | 1080x1080 |
ig-portrait/carousel | 1080x1350 |
ig-story/tiktok/short | 1080x1920 |
linkedin-post | 1200x1200 |
pinterest | 1000x1500 |
youtube-thumb | 1280x720 |
og/blog | 1200x630 |
twitter-header | 1500x500 |
presentation | 1920x1080 |
doc-a4-portrait | 1000x1414 |
certificate | 1414x1000 |
Common mistakes#
- Putting showAt or hideAt inside enter or exit. They are siblings of enter and exit on transitions; inside them the renderer ignores the timing and every layer shows at once. The property is transitions, not animation.
- Mixing unit forms: lengths are px strings ("48px") and lineHeight is a number (1.2).
- Reusing a parameterId on layers of different types on one page (the later one is renamed with _1).
- Several
<p>blocks in one text layer (they overlap), or \n inside rich text (it is not converted; use<br>). - An inline SVG data URI with a raw # or < (it is cut short and renders blank; percent-encode it).
- A shape with backgroundColor and no fill: shapes paint fill, which defaults to black.
- A generic audio parameterType. Swap a soundtrack URL with videoOptions.audioSource; change spoken copy through the track's tts.parameterId.
- videoOptions.trimStart without trimEnd on a render (it needs both).
- Expecting a video layer's own audio to play: its videoOptions.muted defaults to true; set it to false.
Ready to automate?
Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.
Get your API key- Image, PDF and video generation via API
- Visual editor with AI and smart layouts
- Zapier, Make, MCP and 40+ integrations
- White-label embed for your own app
- 100 free credits a month, no credit card required