Overlays Reference
Every overlay type Renderly supports - fields, units, gotchas, and minimal examples. The companion to the OpenAPI spec.
This page is the human-readable companion to the OpenAPI reference. It documents every overlay type a composition can contain, calls out the units each field uses, and flags the surprises (fields that look optional but aren't, fields the renderer silently ignores).
For the literal schema and copy-pasteable examples, see POST /renders.
Fields every overlay has
These come from BaseOverlay and exist on text, video, image, sound, shape, sticker, caption, and effect alike.
| Field | Unit | Purpose |
|---|---|---|
id | integer | Unique numeric id within the composition. |
type | string | Discriminator: "text", "video", "image", "sound", "shape", "sticker", "caption", "effect". |
from | frames | First frame the overlay is visible on the timeline. |
durationInFrames | frames | How long the overlay stays visible. |
row | integer | Timeline track index. Lower row numbers render above higher row numbers - row: 0 sits on top, row: 5 sits at the back. See Row stacking below. |
top, left | pixels | Position of the layer's top-left corner relative to the canvas. |
width, height | pixels | Layer box size. |
rotation | degrees | Rotation around the layer's center. Defaults to 0. |
isDynamic | boolean | Marks the overlay as a template variable. |
name | string | Variable key used to match the replacements object. |
Positioning cheatsheet
top and left are pixels from the top-left corner of the canvas - (0, 0) is the top-left. To pin a 300×300 element to the bottom-right of a 1080×1920 canvas with a 40 px margin:
top = canvasHeight - height - margin → 1920 - 300 - 40 = 1580
left = canvasWidth - width - margin → 1080 - 300 - 40 = 740
Row stacking
row is the timeline track index, and it directly drives stacking order: the renderer computes zIndex = 100 - row * 10, so lower row numbers sit on top.
- A
textoverlay onrow: 0renders above avideooverlay onrow: 1- the video becomes the background for the text. - Background video / full-frame effects belong on the highest row numbers (e.g.
row: 4orrow: 5). - Headlines, CTAs, and anything that should sit in front of everything else belong on
row: 0orrow: 1. - Give each visually-stacked overlay its own row. Two overlays on the same row will collide - they share a z-index, and the order falls back to the array order, which is fragile.
A safe convention for a full composition: row: 5 audio · row: 4 background video/effect · row: 3 scrim/dim shape · row: 2 decorative graphics · row: 1 body text · row: 0 headline.
text
{
"id": 1, "type": "text",
"content": "Hello world",
"from": 0, "durationInFrames": 90,
"row": 1, "top": 800, "left": 60, "width": 960, "height": 200,
"styles": {
"fontSize": "4rem", "fontWeight": "900",
"color": "#FFFFFF", "textAlign": "center",
"animation": { "enter": "fade", "exit": "fade", "enterDuration": 0.4 }
}
}contentis the rendered string. WhenisDynamicis true, this is whatreplacementsswaps.styles.fontSizeaccepts any CSS size string ("3rem","48px").styles.animation- preset enter/exit.enterDuration/exitDurationare in seconds.styles.animatedText,styles.typeWriter,styles.counterMode,styles.codeBlock- alternate text modes, each withenabled: trueto activate. See the editor doc for what each does.
video
{
"id": 0, "type": "video",
"src": "https://example.com/clip.mp4",
"from": 0, "durationInFrames": 180,
"row": 0, "top": 0, "left": 0, "width": 1080, "height": 1920,
"videoStartTime": 2,
"speed": 1,
"styles": {
"objectFit": "cover", "opacity": 1
}
}src- public URL of the source media. Replaced byreplacementsifisDynamicis true.videoStartTime- in-point of the source clip in seconds. Skips the first N seconds of the source.speed- playback speed multiplier.2plays twice as fast;0.5plays at half speed.mediaSrcDuration- total length of the source media in seconds. Helps the editor compute trim boundaries.segments- ordered list of{ startFrame, endFrame, speed? }slices to chain together. When set,videoStartTimeis ignored. Frames are absolute source frames.greenscreen- chroma-key removal.{ enabled: true, sensitivity, threshold: { red, green, blue }, smoothing, spill }.styles.volume- 0-1. Defaults to the source's natural volume; set to0to mute.styles.cropEnabled/cropX/cropY/cropWidth/cropHeight- percentages (0-100) of the source frame to display. SetcropEnabled: trueto apply.
image
{
"id": 2, "type": "image",
"src": "https://example.com/photo.jpg",
"from": 0, "durationInFrames": 90,
"row": 1, "top": 200, "left": 100, "width": 400, "height": 400,
"styles": {
"objectFit": "cover", "borderRadius": "16px", "opacity": 0.85
}
}styles.opacity- 0-1. Combined with the composition'sbackgroundColor, this is how you tint or darken an image.greenscreen- same shape as video.styles.layout3D.layout- tilt the image into 3D. One ofnone,tilt-up,tilt-down,card-left,card-right,book,floating,billboard,skewed. See 3D layouts below.styles.layout3D.settle-truemakes the tilt an animation instead of a fixed pose: the image arrives at the full angle and rotates to flat. See 3D layouts.styles.animation- enter/exit preset with optional duration in seconds.- Crop fields are the same as video.
3D layouts
layout3D is the only depth control, and it works on image overlays only. On its own it is a
fixed pose - the image holds the same tilt for its whole life:
{ "styles": { "layout3D": { "layout": "card-right" } } }Add "settle": true and the tilt becomes a move. The image arrives at the preset's full angle
and rotates to flat over its entry animation, landing squarely on its top/left/width/height
box. This is what you want for a screenshot, product card or device frame that should arrive
in 3D rather than sit in 3D:
{
"id": 4, "type": "image",
"src": "https://example.com/screenshot.png",
"from": 0, "durationInFrames": 120,
"row": 2, "top": 150, "left": 1120, "width": 520, "height": 1040,
"styles": {
"borderRadius": "62px",
"layout3D": { "layout": "card-right", "settle": true },
"animation": {
"enter": "slideLeft",
"enterDuration": 1.15,
"enterEasing": "easeOutCubic"
}
}
}The rotation is timed by animation.enterDuration (seconds) and shaped by animation.enterEasing,
so it runs together with whatever entry animation you pick. easeOutCubic over 1.0-1.4 s reads
well; anything under ~0.6 s is over before the eye registers it.
Pick the preset by which way the image should face. card-left angles the face to the right (use
it on the left of frame), card-right angles it to the left (use it on the right of frame), and
floating is a gentle all-purpose tilt. book and skewed are extreme and only work on small
boxes - under roughly 600 px wide.
Without settle, the pose is static, so pair it with styles.animation.during (sway or float)
to keep it from reading as a still. With settle, keep during subtle - an intensity around 0.12
is enough once the rotation is doing the work.
sound
{
"id": 3, "type": "sound",
"src": "https://example.com/music.mp3",
"from": 0, "durationInFrames": 180,
"row": 2, "top": 0, "left": 0, "width": 0, "height": 0,
"startFromSound": 5,
"speed": 1,
"mediaSrcDuration": 145,
"styles": {
"volume": 0.8, "fadeIn": 1, "fadeOut": 1.5
}
}Sound overlays still need top / left / width / height (they come from the shared BaseOverlay), but they're ignored at render time. Set them to 0.
startFromSound- in-point of the source audio in seconds. Use this to skip an intro.speed- playback speed multiplier.styles.fadeIn,styles.fadeOut- fade durations in seconds.styles.volume- 0-1.
shape
{
"id": 4, "type": "shape",
"content": "rectangle",
"from": 0, "durationInFrames": 90,
"row": 1, "top": 100, "left": 100, "width": 200, "height": 200,
"styles": {
"fill": "#FF0080", "borderRadius": "16px",
"animation": { "enter": "scale", "exit": "fade" }
}
}content- shape identifier. One of"rectangle","circle","triangle","star","diamond","hexagon","arrow","line". Unknown values fall back to"rectangle".styles.gradient- single CSS gradient string.styles.gradientTransition- animated transition between multiple gradient strings.
sticker
{
"id": 5, "type": "sticker",
"content": "discount-50",
"category": "Discounts",
"from": 30, "durationInFrames": 90,
"row": 2, "top": 500, "left": 740, "width": 300, "height": 300,
"styles": { "scale": 1, "animation": { "enter": "bounce" } }
}content- sticker identifier from the sticker library.category- one of"Shapes","Discounts","Emojis","Reviews","Default".styles.scale- uniform scale multiplier.
caption
{
"id": 6, "type": "caption",
"captions": [
{
"text": "Hello world",
"startMs": 0, "endMs": 1200,
"timestampMs": null, "confidence": 0.97,
"words": [
{ "word": "Hello", "startMs": 0, "endMs": 500, "confidence": 0.98 },
{ "word": "world", "startMs": 600, "endMs": 1200, "confidence": 0.96 }
]
}
],
"from": 0, "durationInFrames": 90,
"row": 3, "top": 1500, "left": 60, "width": 960, "height": 200,
"styles": {
"fontSize": "3rem", "color": "#FFFFFF",
"textAlign": "center",
"highlightStyle": { "color": "#FFD700", "scale": 1.1 }
}
}Caption timing is in milliseconds and is interpreted at 30 FPS regardless of the composition's fps. If you build captions at 60 FPS the words will fall out of sync.
captions[]carries one entry per phrase, each with word-level timing.styles.highlightStyleis what the currently-spoken word looks like - karaoke-style emphasis.template- an optional named style template (set by the editor).
effect
{
"id": 7, "type": "effect",
"content": "confetti",
"from": 0, "durationInFrames": 180,
"row": 4, "top": 0, "left": 0, "width": 1080, "height": 1920,
"styles": { "particleCount": 200, "speed": 1, "size": 8 }
}contentis the effect preset: one of"snow","confetti","rain","fireflies","sparkles","bubbles","matrix","grid-stagger","fracture","scrolling-columns".- Each preset takes its own subset of styles. Matrix effects use
matrixFontSize/matrixColor/matrixDensity/matrixCharset; grid effects usegridCols/gridRows/cellColors/cellImages/staggerDelay; scrolling columns usecolumns[]/columnGap/imageHeight.
Composition-level fields
These sit at the top of inputProps, not inside any overlay:
| Field | Unit | Purpose |
|---|---|---|
width, height | pixels | Canvas size. |
fps | frames per second | Composition frame rate. |
durationInFrames | frames | Total composition length. |
backgroundColor | CSS color | Solid fill behind every overlay. Defaults to "white". |
overlays[] | array | The overlays themselves. |
Where to next
- Templates - turn any overlay into a per-render variable
- Rendering - kick off a render job and poll for the result
- API reference - every field with its OpenAPI schema