Dynamic Layouts – Agent API Reference
Dynamic Layouts – Agent API Reference
Audience: This document is structured as a precise data-model reference intended to be pasted into an AI agent's system prompt. It describes the JSON shape of a Dynamic Layout exactly as consumed by the VenueVue signage renderer.
When invoking an agent, prepend this entire document with a short task instruction such as: "You generate VenueVue Dynamic Layout JSON. Use only the fields described in the reference below. Output a single JSON object matching the
DynamicLayoutschema. Do not invent fields."
High-level model
A DynamicLayout is a top-level record describing a canvas and an ordered list of elements that render on it.
{
"name": "string", // Friendly name (required)
"canvasWidth": 1920, // Design pixels (default 1920)
"canvasHeight": 1080, // Design pixels (default 1080)
"duration": null, // Optional total runtime in seconds; null = loop forever
"elements": [ /* Element[] */ ] // Ordered list, lower index renders first (z-index also applies)
}
canvasWidth × canvasHeight defines the design pixel coordinate space. At playback, the renderer scales the entire canvas to fit the display. Pixel positions inside elements (positionUnits === 'px') are interpreted against this design canvas, not the physical display.
Element — common fields
Every element in elements[] (and every element inside an inline group's data.elements[]) shares the following shape. Type-specific fields are layered on top — see the next section.
{
"id": "el_<timestamp>_<counter>", // Required, must be unique within the parent elements array
"name": "string", // Friendly label; "" allowed
"type": "asset" | "widget" | "html" | "url" | "shape" | "dynamicLayout" | "playlist",
"zIndex": 0, // Higher renders on top; ties break by array order
// ----- Position (design canvas) -----
"position": {
"top": 10, // Numeric value
"left": 10,
"width": 30,
"height": 30
},
"positionUnits": { // Unit per dimension
"top": "%" | "px",
"left": "%" | "px",
"width": "%" | "px",
"height": "%" | "px"
},
// ----- Visibility breakpoints (against the RENDERED canvas pixel size) -----
"breakpoints": {
"minWidth": null, // Hide if rendered width < this
"maxWidth": null, // Hide if rendered width > this
"minHeight": null,
"maxHeight": null
},
// ----- Display -----
"fitMode": "fill", // 'fill' | 'contain' | 'cover' | 'stretch' | 'tile'
"rotation": 0, // Degrees; positive = clockwise
"locked": false, // Canvas safety lock; does NOT affect playback
// ----- Drop shadow (applies to every element type) -----
"shadowEnabled": false, // Master toggle
"shadowX": 0, // Horizontal offset (px against design canvas)
"shadowY": 4, // Vertical offset (px)
"shadowBlur": 16, // Blur radius in px (>=0)
"shadowOpacity": 0.5, // 0–1
"shadowColor": "#000000", // Hex; alpha taken from shadowOpacity
// ----- Lifecycle -----
"delayIn": 0, // Seconds before this element starts entering
"transitionIn": "none", // See TRANSITION_TYPES
"transitionInDuration": 0, // Seconds; ignored when transitionIn === 'none'
"duration": null, // Seconds the element stays visible (null = full layout)
"transitionOut": "none", // See TRANSITION_TYPES
"transitionOutDuration": 0,
"easingType": "easeInOutCubic", // See EASING_TYPES
// ----- Keyframes (optional) -----
"keyframes": [ /* Keyframe[] */ ]
}
Defaults
When a field is omitted, the renderer uses the defaults shown above. Notable defaults:
position:{ top: 10, left: 10, width: 30, height: 30 }in%positionUnits: all%breakpoints: allnullfitMode:'fill'rotation:0shadowEnabled:false(whentrue, defaults:shadowX: 0,shadowY: 4,shadowBlur: 16,shadowOpacity: 0.5,shadowColor: '#000000')delayIn:0transitionIn/transitionOut:'none'transitionInDuration/transitionOutDuration:0duration:nulleasingType:'easeInOutCubic'
TRANSITION_TYPES
'none' | 'fade' | 'slideLeft' | 'slideRight' | 'slideUp' | 'slideDown' | 'zoomFade' | 'scaleUp'
When transitionIn or transitionOut is 'none', the corresponding duration is ignored (treated as 0). The element is fully visible at t=0 of its lifecycle.
EASING_TYPES
'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'
FIT_MODES
'fill' // Crop to fill the box, preserving aspect ratio
'contain' // Letterbox to fit inside the box, preserving aspect ratio
'cover' // Same as fill in most cases (CSS object-fit semantics)
'stretch' // Stretch to fill the box without preserving aspect ratio
'tile' // Repeat to fill the box
Element types
Pick a type and add the type-specific fields below. Fields not listed for a type are ignored by the renderer for that type but are preserved when the element round-trips through the editor.
type: "asset"
Renders an image or video from the media library.
{
"type": "asset",
"assetId": "asset_xxxxx", // Required for visible content
"fitMode": "fill" // See FIT_MODES
}
type: "widget"
Renders a built-in dynamic widget.
{
"type": "widget",
"widgetType": "clock" | "weather" | "countdown" | "qrcode" | "text",
"widgetPreset": "default", // ID of a preset for the chosen widgetType
"widgetSettings": { /* widget-specific overrides on top of the preset */ }
}
Widget types and their widgetSettings keys
clock
{
"format": "24h" | "12h",
"showSeconds": true,
"showDate": false,
"dateFormat": "ddd, MMM D",
"timezone": "Australia/Brisbane", // Any IANA timezone, or null for display-local
"font": "digital" | "modern" | "classic" | "monospace",
"padding": 5, // Percentage 0–50
"textColor": "#ffffff",
"backgroundColor": "transparent",
"textGlow": false,
"glowColor": "#00ff66"
}
weather
{
"location": "Brisbane", // City name or "lat,lon"
"units": "metric" | "imperial",
"showCondition": true,
"showHumidity": false,
"showWind": false,
"showIcon": true,
"layout": "horizontal" | "vertical" | "compact",
"refreshMinutes": 15, // 5–60
"padding": 0,
"textColor": "#ffffff",
"backgroundColor": "transparent"
}
countdown
{
"target": "2026-12-31T23:59:00+10:00", // ISO timestamp
"title": "New Year",
"showDays": true,
"showHours": true,
"showMinutes": true,
"showSeconds": true,
"showLabels": true,
"completedMessage": "Happy New Year!",
"font": "digital" | "modern" | "classic" | "monospace",
"padding": 0,
"textColor": "#ffffff",
"backgroundColor": "transparent",
"textGlow": false,
"glowColor": "#ff3366"
}
qrcode
{
"content": "https://example.com",
"label": "Scan to view",
"showLabel": true,
"size": 80, // 40–100 (% of zone)
"errorCorrection": "L" | "M" | "Q" | "H",
"padding": 0,
"qrColor": "#000000",
"qrBackground": "#ffffff",
"labelColor": "#ffffff",
"labelBackground": "transparent"
}
text
{
"text": "<p>Hello world</p>", // Rich HTML allowed
"fontSize": "auto" | 24 | "24px" | "32px" | "48px" | "64px" | "96px" | "128px" | "192px" | "256px",
"fontFamily":
"roboto" | "serif" | "monospace" | "segment" // Legacy preset string
| { "family": "Georgia" } // Real font family (system or web-available)
| { "family": "Inter", "weight": "700", // FontPicker object — when `files` is present
"files": { "700": "https://..." } }, // the widget injects @font-face automatically
"fontWeight": "light" | "normal" | "bold",
"fontStyle": "normal" | "italic", // SVG-import preserves italic
"letterSpacing": 0, // Number → px, or CSS length string ('0.05em', '2px')
"textAlign": "left" | "center" | "right",
"lineHeight": 1.2, // 1.0–2.5
"padding": 0,
"textColor": "#ffffff",
"backgroundColor": "transparent",
"textShadow": false, // Drop shadow on glyphs
"glowColor": "#3f8cff"
}
fontFamilyforms. The legacy preset strings ('roboto','serif','monospace','segment') map to bundled stacks. To use a specific typeface, pass an object:{ family: 'Georgia' }for system / web-safe fonts, or{ family, weight, files: { <weight>: <url> } }to ship a webfont via@font-face. SVG import uses the object form automatically for any system family it recognises (Georgia, Times New Roman, Arial, Helvetica, Verdana, etc.).
type: "html"
Raw HTML/CSS rendered inside the element box.
{
"type": "html",
"html": "<div style=\"width:100%;height:100%;\">...</div>"
}
Use width:100%; height:100% on your root container to fill the box. Inline styles are recommended.
type: "url"
Embedded iframe.
{
"type": "url",
"url": "https://dashboard.example.com"
}
type: "shape"
Geometric primitive rendered as SVG (or, for rectangles, as a <div> with CSS borders so the corner radius stays circular under non-uniform stretching).
{
"type": "shape",
"shapeType": "rectangle" | "circle" | "triangle" | "diamond" | "pentagon" | "hexagon" | "star" | "arrow-right" | "arrow-left" | "heart" | "line" | "svg-path",
"shapeFillColor": "#3f8cff", // 6- or 8-char hex (`#RRGGBB` / `#RRGGBBAA`)
"shapeBorderColor": "#ffffff",
"shapeBorderWidth": 0, // px against the design canvas
"shapeBorderStyle": "solid", // 'solid' | 'dashed' | 'dotted'
"shapeBorderDashArray": "", // Optional explicit pattern, e.g. "10 14"
// (space-separated lengths in px). When non-empty
// and `shapeBorderStyle !== 'solid'`, overrides
// the default derived pattern. Set automatically
// by SVG import to preserve `stroke-dasharray`.
"shapeCornerRadius": 0, // Rectangle only. 0–50, interpreted as a percentage
// of the *shorter* rendered side, so pill buttons
// (50) stay pill-shaped at any size.
"shapePreserveAspect": "none", // 'none' | 'contain' | 'cover'
// 'none' — stretch shape to the box (default, back-compat)
// 'contain' — keep aspect, letterbox inside the box
// 'cover' — keep aspect, crop to fill the box
"svgPath": null, // Required when shapeType === 'svg-path'; SVG path 'd' attribute
"svgViewBox": "0 0 100 100", // Optional viewBox for custom svg-path shapes
"svgFillRule": "nonzero" // 'nonzero' | 'evenodd' — for svg-path
}
Aspect preservation: Non-rectangular shapes (stars, arrows, hearts, custom SVG paths) distort when the element's aspect ratio changes between layouts (landscape vs portrait, different zone sizes, etc.). Set
shapePreserveAspect: "contain"to keep them symmetrical; reserve"none"(the default) for shapes designed to span and stretch — wide diagonal stripes, full-width ribbons, etc.
Border style limitations: CSS
border-stylehonours the keywords (dashed/dotted) but not arbitrary patterns — so for the rectangle case the renderer paints a CSS border for solid strokes and switches to an SVG overlay whenshapeBorderDashArrayis set. SVG-path / circle / polygon shapes always usestroke-dasharraydirectly.
type: "dynamicLayout" (Group)
A container element. Either embeds children inline OR references another saved dynamic layout.
{
"type": "dynamicLayout",
"dynamicLayoutId": null, // If set, renders the referenced layout (children ignored)
"data": {
"canvasWidth": 1920, // Design size of the inline children
"canvasHeight": 1080,
"duration": null, // Optional duration override
"elements": [ /* Element[] */ ] // Inline children (recursive)
}
}
Rules:
- If
dynamicLayoutIdis non-null, the renderer fetches that layout;data.elementsare ignored at playback. - The renderer detects and prevents circular references via
dynamicLayoutIdchains. - Inline children use the SAME element schema recursively. Nesting depth is unlimited but performance degrades with depth.
type: "playlist"
Embeds one or more device playlists inside the element box. Only valid when the dynamic layout is attached to a vvu-cs-app device.
{
"type": "playlist",
"playlistIds": ["layout_xxx", "layout_yyy"] // Layouts where type === 'playlist'
}
Keyframes
A keyframe is a snapshot of animatable properties at a specific time. Keyframes are stored on the element under keyframes[] and interpolate between consecutive entries.
{
"id": "kf_<timestamp>", // Required, unique within the element
"time": 2.5, // Seconds from the start of the layout timeline
"easing": "easeInOutCubic", // Easing INTO the next keyframe; see EASING_TYPES
"props": {
"position": { // Partial — only changed dimensions need to be present
"top": 20,
"left": 50,
"width": 40,
"height": 40
},
"rotation": 15, // Degrees
"opacity": 1, // 0–1
"scale": 1, // Optional uniform scale
"filter": { // Optional CSS filter parts
"blur": 0, // px
"brightness": 1, // 1 = unchanged
"contrast": 1
}
}
}
Keyframe rules
timeis relative to the layout timeline, not the element's lifecycle.- Keyframes must be sorted by
timeascending at runtime; the editor sorts on save. - The element's base properties are used before the first keyframe and after the last keyframe unless those keyframes pin the values explicitly.
- Easing
easinglives on the LEFT keyframe in a pair (it controls the curve INTO the next keyframe). propsonly needs to include the fields the keyframe changes — undefined fields fall back to the element's base values.
Element identity rules
idmust be unique within its containing array. The editor uses the formel_<unix_ms>_<counter>; agents may emit any unique string.- Keyframe
idmust be unique within its element'skeyframes[]. The editor uses the formkf_<unix_ms>. - When generating a new layout, pick IDs that won't collide between elements. A simple scheme:
"el_g_001","el_g_002","kf_g_001_a", etc.
Validation rules an agent must follow
- Required fields:
name,canvasWidth,canvasHeight,elements. - Element required fields:
id,type,position,positionUnits. - Type values: Must be one of the listed
typevalues. No custom types. positionUnitsvalues: Each dimension must be'%'or'px'. No other units.transitionIn/transitionOut: Must be one ofTRANSITION_TYPES. When'none', the corresponding duration MUST be0(renderer enforces this, but emit0for cleanliness).easingType: Must be one ofEASING_TYPES.- Shape
shapeType: Must be one of the shape values listed. - Widget
widgetType: Must be one of'clock' | 'weather' | 'countdown' | 'qrcode' | 'text'. Settings underwidgetSettingsMUST match the chosenwidgetType's schema. - Groups: If
dynamicLayoutIdis set, leavedata.elementsempty ([]). Do not set both with the intent of merging. - Playlist: Only emit
type: "playlist"elements when you have validplaylistIds. Do not invent IDs. - Asset: Only emit
assetIdvalues that exist in the target organisation's media library. Leavenullif creating a placeholder. - No extra fields: Do not invent new top-level or element fields. The editor preserves unknown fields but the renderer ignores them, which is misleading.
- Numeric ranges:
rotation: any number (degrees)position.*:%values typically 0–100 but may be negative (off-canvas for animations) or > 100position.*withpx: bounded only by sensible canvas sizedelayIn,*Duration,duration, keyframetime: non-negative secondsopacity: 0–1breakpoints.*: positive px ornull
- Sort order: Within
elements[], you may rely onzIndexfor stacking. Do not depend on array order for stacking — but DO rely on it for tiebreaks. - Drop shadow: When
shadowEnabledisfalse(or omitted), the renderer ignores all othershadow*fields. Whentrue, allshadow*numeric fields must be finite andshadowOpacitymust be in[0, 1]. Shadows compose on top of any keyframefilteranimation. - Shape aspect:
shapePreserveAspectmust be one of'none' | 'contain' | 'cover'. Default'none'preserves legacy stretch behaviour. ForshapeType: 'svg-path',svgPathis required andsvgViewBoxdefaults to'0 0 100 100'. - Shape border:
shapeBorderStylemust be one of'solid' | 'dashed' | 'dotted'.shapeBorderDashArray, when supplied, must be a space- or comma-separated list of positive numbers (e.g."10 14"). It only takes effect whenshapeBorderStyle !== 'solid'andshapeBorderWidth > 0. - Text widget
fontFamily: May be a preset string ('roboto','serif','monospace','segment') OR a FontPicker object{ family: string, weight?: string, files?: { [weight]: url } }. Do not emit other preset strings. - Group children: Inside
data.elements[], child positions and pixel-sized widget settings (e.g. textfontSizein px) are interpreted againstdata.canvasWidth×data.canvasHeight— NOT against the parent layout's canvas. Keepdata.canvasWidth/Heightin sync with the group element's intended pixel area for predictable rendering.
Minimal valid layout
{
"name": "Hello Layout",
"canvasWidth": 1920,
"canvasHeight": 1080,
"elements": [
{
"id": "el_g_001",
"name": "Title",
"type": "widget",
"widgetType": "text",
"widgetPreset": "default",
"widgetSettings": {
"text": "<p>Hello, world</p>",
"fontSize": "auto",
"textAlign": "center",
"textColor": "#ffffff",
"backgroundColor": "#000000"
},
"zIndex": 0,
"position": { "top": 30, "left": 10, "width": 80, "height": 40 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"breakpoints": { "minWidth": null, "maxWidth": null, "minHeight": null, "maxHeight": null },
"fitMode": "fill",
"rotation": 0,
"locked": false,
"delayIn": 0,
"transitionIn": "none",
"transitionInDuration": 0,
"duration": null,
"transitionOut": "none",
"transitionOutDuration": 0,
"easingType": "easeInOutCubic"
}
]
}
Worked example — animated promo card
A title that fades in, a body that slides up, and a QR code that scales up — staggered. Total scene length 4s.
{
"name": "Promo Card",
"canvasWidth": 1920,
"canvasHeight": 1080,
"duration": 4,
"elements": [
{
"id": "el_bg",
"name": "Background",
"type": "shape",
"shapeType": "rectangle",
"shapeFillColor": "#1e3a8a",
"shapeCornerRadius": 24,
"zIndex": 0,
"position": { "top": 10, "left": 10, "width": 80, "height": 80 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"transitionIn": "none",
"transitionOut": "none"
},
{
"id": "el_title",
"name": "Title",
"type": "widget",
"widgetType": "text",
"widgetPreset": "headline",
"widgetSettings": {
"text": "<p>Summer Sale</p>",
"fontSize": "auto",
"fontWeight": "bold",
"textAlign": "center",
"textColor": "#ffffff"
},
"zIndex": 10,
"position": { "top": 18, "left": 15, "width": 70, "height": 20 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"delayIn": 0.2,
"transitionIn": "fade",
"transitionInDuration": 0.6,
"duration": 3.0,
"transitionOut": "fade",
"transitionOutDuration": 0.4,
"easingType": "easeOutCubic"
},
{
"id": "el_body",
"name": "Body",
"type": "widget",
"widgetType": "text",
"widgetPreset": "default",
"widgetSettings": {
"text": "<p>Up to 50% off all bottled wines this weekend.</p>",
"fontSize": "48px",
"textAlign": "center",
"textColor": "#dbeafe"
},
"zIndex": 10,
"position": { "top": 42, "left": 15, "width": 70, "height": 18 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"delayIn": 0.6,
"transitionIn": "slideUp",
"transitionInDuration": 0.5,
"duration": 2.7,
"transitionOut": "fade",
"transitionOutDuration": 0.4,
"easingType": "easeOutCubic"
},
{
"id": "el_qr",
"name": "QR",
"type": "widget",
"widgetType": "qrcode",
"widgetPreset": "default",
"widgetSettings": {
"content": "https://example.com/sale",
"label": "Scan to view offers",
"size": 90,
"errorCorrection": "Q",
"qrColor": "#000000",
"qrBackground": "#ffffff"
},
"zIndex": 10,
"position": { "top": 64, "left": 40, "width": 20, "height": 22 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"delayIn": 1.0,
"transitionIn": "scaleUp",
"transitionInDuration": 0.6,
"duration": 2.3,
"transitionOut": "fade",
"transitionOutDuration": 0.4,
"easingType": "easeOutCubic"
}
]
}
Worked example — keyframed motion
A logo that moves diagonally across the screen and rotates 360° over 5 seconds with two keyframes.
{
"name": "Logo Sweep",
"canvasWidth": 1920,
"canvasHeight": 1080,
"duration": 5,
"elements": [
{
"id": "el_logo",
"name": "Logo",
"type": "asset",
"assetId": "asset_logo_main",
"fitMode": "contain",
"zIndex": 1,
"position": { "top": 10, "left": 5, "width": 15, "height": 15 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"rotation": 0,
"transitionIn": "none",
"transitionOut": "none",
"easingType": "easeInOutCubic",
"keyframes": [
{
"id": "kf_a",
"time": 0,
"easing": "easeInOutCubic",
"props": {
"position": { "top": 10, "left": 5, "width": 15, "height": 15 },
"rotation": 0,
"opacity": 1
}
},
{
"id": "kf_b",
"time": 5,
"easing": "linear",
"props": {
"position": { "top": 75, "left": 80, "width": 15, "height": 15 },
"rotation": 360,
"opacity": 1
}
}
]
}
]
}
Agent generation checklist
When asked to generate a layout from a natural-language brief, an agent should:
- Resolve canvas size from the brief (default
1920×1080if unspecified). - Plan a layout at the percentage level. Keep elements within
0–100on each axis unless intentionally animating off-canvas. - Choose element types strictly from the listed
typevalues. - Choose widgets strictly from the five widget types; fill
widgetSettingswith the documented keys for the chosen widget. - Reference assets only by ID if the brief or context provides them. Otherwise leave
assetId: nulland put ashapeortextplaceholder. - Stagger lifecycle for animated scenes by tuning
delayInper element. - Use keyframes only when interpolation is needed — for simple in/out, prefer lifecycle transitions.
- Set
transition*to'none'with0duration when not animating; this matches the editor's defaults and avoids accidental fades. - Emit complete element objects with all common fields, even if equal to defaults. This avoids ambiguity for the renderer.
- Output only the JSON object in the response, with no surrounding markdown unless explicitly requested.
Embedding in a Signage Campaign Component
A campaign component can host a dynamic layout inline (the elements are stored on the component itself, not in the device's dynamicLayouts collection). This is the path most agents will use when asked to "update the dynamic layout on this campaign component" — the data lives at well-known paths on the CampaignComponent document.
Campaign component shape
{
"id": "cc_xxxxx",
"type": "signage",
"data": {
"content": {
// ===== When data.settings.contentMode === "asset" =====
"assetId": "asset_xxxxx", // Single media asset path
// ===== When data.settings.contentMode === "dynamicLayout" =====
"canvasWidth": 1920, // Design canvas width (default 1920)
"canvasHeight": 1080, // Design canvas height (default 1080)
"elements": [ /* Element[] */ ] // Same Element schema as a top-level DynamicLayout
},
"settings": {
"contentMode": "asset" | "dynamicLayout", // Switches which `content` fields are used
"duration": 10, // Seconds the component plays each time selected
"frequency": 1, // Play 1 in every X rotations (>=2, or 0/null = once)
"priority": false, // Priority overrides non-priority components
"fitMode": "fill", // FIT_MODES
"transitionType": "fade", // TRANSITION_TYPES (component-level transition)
"transitionDuration": 0.5, // Seconds
"easingType": "easeInOutCubic", // EASING_TYPES
"repeat": 0, // Extra repeats; 0 = play once
"layouts": ["layout_xxx", ...] // Device playlist layouts this component is eligible for
}
}
}
Where dynamic layout data lives on a component
| Field | Path on the campaign component | Notes |
|---|---|---|
| Mode toggle | data.settings.contentMode | Set to "dynamicLayout" to enable inline-layout fields |
| Canvas width | data.content.canvasWidth | Defaults to 1920 if missing |
| Canvas height | data.content.canvasHeight | Defaults to 1080 if missing |
| Elements | data.content.elements | Array of Elements, exactly as defined above |
| Component duration | data.settings.duration | MUST be ≥ the dynamic layout's full timeline length |
| Component transition | data.settings.transitionType + transitionDuration + easingType | Plays between this component and the previous one — separate from per-element transitions inside the layout |
| Component fit | data.settings.fitMode | How the layout output is fitted to the target zone |
| Eligible zones | data.settings.layouts | Restricts which device playlist layouts may select this component |
Inline layout vs linked dynamic layout
| Approach | How | When to use |
|---|---|---|
| Inline (this section) | Set data.settings.contentMode = "dynamicLayout" and write the elements straight into data.content.elements | One-off creative tied to a specific campaign |
| Linked | Build the layout under Marketing > Signage > Dynamic Layouts (a standalone DynamicLayout record), then reference it from a dynamicLayout-type element inside the inline data.content.elements ({ "type": "dynamicLayout", "dynamicLayoutId": "<id>" }) | Reusable scenes shared across multiple campaigns |
Tip for agents: When asked to update a campaign component's creative, write into
data.content.elements. When asked to update a reusable scene, write into the standaloneDynamicLayoutrecord'selements. Both arrays use the SAME element schema; nothing else changes.
Minimal "dynamic layout" campaign component
{
"data": {
"settings": {
"contentMode": "dynamicLayout",
"duration": 6,
"frequency": 0,
"priority": false,
"fitMode": "fill",
"transitionType": "fade",
"transitionDuration": 0.4,
"easingType": "easeInOutCubic",
"repeat": 0,
"layouts": ["layout_main_zone"]
},
"content": {
"canvasWidth": 1920,
"canvasHeight": 1080,
"elements": [
{
"id": "el_g_001",
"name": "Headline",
"type": "widget",
"widgetType": "text",
"widgetPreset": "headline",
"widgetSettings": {
"text": "<p>Welcome</p>",
"fontSize": "auto",
"textAlign": "center",
"textColor": "#ffffff"
},
"zIndex": 0,
"position": { "top": 30, "left": 10, "width": 80, "height": 40 },
"positionUnits": { "top": "%", "left": "%", "width": "%", "height": "%" },
"transitionIn": "fade",
"transitionInDuration": 0.5,
"duration": 5,
"transitionOut": "fade",
"transitionOutDuration": 0.4,
"easingType": "easeInOutCubic"
}
]
}
}
}
Validation rules specific to campaign components
- When
data.settings.contentMode === "dynamicLayout",data.content.elementsMUST be an array (may be empty). - When
contentMode === "asset",data.content.assetIdSHOULD be set;data.content.elementsis ignored at playback. data.settings.durationMUST be ≥ the dynamic layout's full animation length (longestdelayIn + transitionInDuration + duration + transitionOutDurationacross elements). Otherwise the layout is cut off mid-animation.data.settings.frequency:0ornullmeans play once; otherwise must be>= 2.data.settings.layoutsis required for the component to be eligible for any zone.- The element schema inside
data.content.elementsis identical to top-levelDynamicLayout.elements— including all enum values, defaults, and validation rules from the rest of this document.