Skip to content

Templates and fields

A template is a locked design plus a field schema. The schema is the contract you expose — it decides exactly what a caller may change, and nothing else can move.

Anatomy#

PropertyMeaning
kindimage uses layers; video uses a timeline
fieldsWhat callers may set — the public contract
layersThe image composition (locked)
timelineThe video composition (locked)
statusdraft or published — only a published template can be rendered
visibilityprivate or public — who may copy it
categoryIdThe main category. Required; a subcategory is optional
versionIncrements on every change; renders record which version they used

Field types#

TypeAcceptsConfig options
textstringmaxLength, minLength, pattern
longtextstringmaxLength, minLength
richtextHTML stringmaxLength
numbernumbermin, max, step
slidernumbermin, max, step
toggleboolean
selectstringoptions[]
multiselectstring[]options[]
colorhex, rgb(), hsl()palette, allowAlpha
gradientobject
imageURL or data URIaspectRatio, minWidth, minHeight
videoURLmaxDurationSec
audioURLmaxDurationSec
lottieURL to JSON
dateISO date stringformat
url / email / phonestringvalidated by shape
json / chart / tableobject or JSON string
qrstring

Values are validated before anything is charged. Failures return 422 TEMPLATE_VALIDATION_FAILED with a per-field errors object, so you can fix an entire payload in one round trip rather than discovering problems one at a time.

A date field pairs with the dynamic video assets — see Dynamic content in the video guide. Bind one to a countdown’s to and each render counts toward the deadline that render was given; bind it to a datetime’s value and the same template stamps a different date every time. Leave the asset at "now" instead and it needs no field at all — it takes the moment the render runs.

Bindings#

A binding points a field at a property pathon a layer or clip. This is what makes templates open-ended: a field is not limited to swapping text or an image — it can drive font size, position, colour, a clip’s trim point, or a transition name.

{
  "key": "headline",
  "label": "Headline",
  "type": "text",
  "required": true,
  "config": { "maxLength": 42 },
  "bindings": [
    { "target": "title", "path": "text" },
    { "target": "shadow", "path": "text" },
    { "target": "title", "path": "style.fontSize",
      "transform": "number", "factor": 0.9 }
  ]
}
PropertyMeaning
targetLayer id (image templates) or clip alias (video templates)
pathDot notation on the layer or clip — text, style.fontSize, asset.src, offset.x
transformuppercase, lowercase, titlecase, trim, number, boolean
factorMultiplier — maps a 0–100 slider onto real units
offsetAdded after the factor: value × factor + offset
skipIfEmptyDefault true — an empty value leaves the design’s own value in place
Video clips are addressed by alias, not by position. That means reordering a timeline never silently breaks a template — an alias that no longer exists is skipped rather than writing to the wrong clip.

Organising the editor#

Groups and order

group puts a field in a named panel; order sorts within it; advanced: true hides it behind a disclosure. The template’s fieldGroups array sets the panel order.

Conditional visibility

Show a field only when another has a particular value.

visibleWhen
{
  "key": "discountBadge",
  "type": "text",
  "visibleWhen": { "field": "showDiscount", "equals": true }
}

// Also supported:
// "visibleWhen": { "field": "layout", "in": ["hero", "split"] }
// "visibleWhen": { "field": "mode", "notEquals": "minimal" }
A hidden field is never treated as required. Demanding a value the UI did not show would be a trap, so validation skips it.

Audience

editorOnly: true shows a field in the studio but hides it from the API schema. apiOnly: true does the reverse — useful for values your systems supply that a human should never type.

Image layers#

An image template is a stack of layers, painted in array order — the first is at the back. Every layer shares a frame (position, size, rotation, opacity, blend mode, effects); the rest of its properties depend on its type.

TypeDraws
textA run of type. Also carries the {{variable}} highlight styling.
dateText, formatted from the render’s own clock via dateTimeFormat
imageA picture, with fit, frame, adjustments and optional background removal
shapeOne of the built-in silhouettes, named by shapeType
iconVector geometry, recoloured by fillColor — not a picture of an icon
gradientA colour wash — a scrim over a photo, or a full-bleed backdrop
chart / table / widgetData, rendered by the shared infographic engine

The frame — every layer has these

PropertyMeaning
x, y, width, heightDesign pixels, in the template’s own coordinate space
opacityAccepted on either scale: 0–1 or 0–100. Anything at or below 1 is read as a fraction.
rotationDegrees, clockwise, about the layer’s centre
flipX, flipYMirror the layer on either axis
perspectiveMap the layer onto four corners instead of its own box — { tl, tr, br, bl }, each an [x, y] pair given as a fraction of the layer. { tl: [0,0], tr: [1,0], br: [1,1], bl: [0,1] } is the box itself and changes nothing. This is what puts artwork onto an angled surface in a product mockup; pair it with surface below. Corners that cross or collapse are ignored rather than drawn.
surfaceCarry the light of the photo underneath across this layer, so artwork on a mockup sits under the product’s own folds and highlights instead of on top of them. { source, x, y, width, height, fit, rotation, shadow, highlight, displace, occlusion } source is the image to take the light from and the box is where it sits, in design pixels, normally copied from the layer beneath. shadow and highlight are 0–100 and default to 100 and 45. The blend is confined to this layer, so the photo beneath is never darkened twice. displace (0–100, default 0) goes further and moves the artwork with the surface’s folds, so a straight line printed across a crease comes out bent — exact on a flat-on shot, and approximated from the print area’s bounding box when the layer is also pinned with perspective. occlusion is a cut-out of anything that passes in front of the artwork — a hand, a strap, a fold. It is drawn over the layer at normal blend, so a transparent PNG of the object is used directly, with no mask to invert. All of it is confined to the layer.
blendModeAny CSS blend — multiply, screen, overlay, and the rest
effectsShadow, glow, outline, blur and backdrop blur — see below
visible, lockedSkip the layer entirely; freeze it in the editor

Effects

effects applies to any layer type, not just pictures — the renderer puts it on the layer frame, above whatever the layer happens to draw. A glow follows the actual silhouette, so a glowing star is a glowing star rather than a glowing rectangle.

effects
{
  "effects": {
    "shadow":  { "enabled": true, "x": 0, "y": 20, "blur": 48,
                 "spread": -8, "color": "#0F172A", "opacity": 30 },
    "glow":    { "enabled": true, "blur": 24, "color": "#8B3DFF", "opacity": 80 },
    "outline": { "enabled": true, "width": 3, "color": "#171C26" },
    "blur": 0,
    "backdropBlur": 18
  }
}
On a text layer, outlinestrokes the glyphs rather than bordering the box — that is what makes hollow display type possible. On a shape it is the shape’s own stroke, and it can be dashed or dotted.

Shapes

shapeType names a silhouette. Rectangles and ellipses are drawn as boxes so borderRadius still applies to them; everything else is a path that stretches with the layer.

GroupValues
Basicrect, rounded, circle, ellipse, squircle
Geometrictriangle, triangle-down, diamond, pentagon, hexagon, hexagon-flat, octagon, parallelogram, trapezoid, cross, star5, star6, star8, burst
Organicheart, drop, shield, cloud, blob1, blob2, blob3, semicircle, quarter, arch, ring
Arrows & bannersarrow-right, arrow-left, arrow-up, chevron, banner, ribbon
Calloutsspeech, speech-round
Dimensionaliso-cube, iso-cylinder, sphere, coin, price-tag, gift, map-pin, megaphone-3d, bag-3d, rocket-3d, trophy-3d, crown-3d, gem-3d, bolt-3d, flame-3d, bell-3d, parcel-3d, card-3d, clock-3d, growth-3d, target-3d, percent-3d, cart-3d, play-3d
Celebrationconfetti, confetti-colour, balloons, bunting, firework-colour, party-hat, streamer
Linesrule, rule-vertical, rule-diagonal, rule-double, rule-arc, rule-curve, rule-s, rule-wave, rule-zigzag, rule-bracket, rule-corner
Connectorsconnector-line, connector-elbow, connector-elbow-v, connector-step, connector-curve

Lines and connectors

A line is a shape, but it is drawn as a strokerather than as a filled outline — which is what lets it carry a dash pattern, a cap and an arrowhead. The geometry is generated at the layer’s real size, so the thickness is even along the whole path and a dash stays the length it was asked for however long the line is.

PropertyMeaning
lineWidthThickness in design pixels. Defaults to 3.
lineStylesolid, dashed, dotted, dash-dot, long-dash, fine. Dash lengths follow the thickness, so the pattern still reads on a heavy line.
lineCapbutt, round, square
lineStart, lineEndnone, arrow, triangle, circle, square, diamond, bar. Sized against the thickness and rotated to follow the path, so a curved connector’s head points along the curve.
connectConnectors only: { from, to, fromSide, toSide }. from and to are layer ids; the sides are auto (the default), top, right, bottom or left.

A pinned connector stores the relationship, not the coordinates. Its box is recomputed from the two elements immediately before layout, so an arrow between two cards stays between them even when a field moves one of them on this particular render. Pinning to a layer that no longer exists is not an error — the connector keeps the box it was drawn with.

The colour comes from fillColor, not borderColor: to whoever is reading the design there is one line colour, and the arrowheads are painted in it too.

Widgets, and binding data to them

A widget draws a single number: a progress bar, a ring, a gauge or a KPI. It has three colours, because it is three things — the filled part, the unfilled track behind it, and the type on top.

PropertyMeaning
widgetTypeprogress, ring, gauge, kpi
widgetValue, widgetMaxThe number, and what it is out of. Defaults to 100.
widgetLabel, widgetSuffixCaption under the value, and what follows it — usually %.
fillColorThe filled part — the bar, the arc.
widgetTrackColorThe UNFILLED part. Defaults to a light slate.
textColorThe value and its label.
The track is the larger half of the shape at low values, so a widget on a dark design needswidgetTrackColor set — otherwise a pale band runs through it.

Binding a field to a widget layer sends a value on every render, which is the point of putting one on a template. A plain number is enough — 72 or 72% sets widgetValue. A JSON object sets anything else on the layer, so one field can move the value and recolour it at once. A color field bound to the layer sets fillColor, the same as it does for a shape.

binding a widget
{ "completion": "72" }

// or, to set more than the value:
{ "completion": "{"widgetValue":72,"fillColor":"#00C853"}" }

The same applies to chart and table layers: a field carrying JSON replaces their data, and a colour field recolours them.

Type and legends on a chart layer

A chart, table or widget layer is drawn by the same renderer the video engine uses, so the same JSON produces the same pixels as a still, in a video, or in the mobile app. Its labels are derived from fontSize — a title is that plus three, an axis tick one less — which is a good default and a poor ceiling. These fields are the ceiling coming off.

{
  "type": "chart",
  "chartType": "bar",
  "chartTitle": "Enquiries by month",
  "chartData": { "labels": ["Jun","Jul","Aug"], "values": [18,34,27] },
  "chartFontScale": 1.5
}

// One number. Labels, ticks, the legend and its
// swatches all grow together, and the left gutter
// widens to fit the bigger ticks.
PropertyDoes
chartFontScaleMultiplies every piece of type the layer draws — a table’s rows and a widget’s readout included. 1 is the chart as drawn.
chartTextStylesPer-role typography, keyed by role: title, axis, labels, values, legend, header, cell, value, caption. Each takes { family, size, scale, weight, color, opacity, letterSpacing, transform, align, offset, hidden }.
chartLegend{ position, align, direction, swatch, swatchSize, gap, itemGap, rowGap, size }. position is one of top | bottom | left | right; the plot gives up the room rather than being drawn over.
chartGridColorThe gridlines behind an axis plot. showGrid owns whether they exist.
chartAxisTicksDivisions on the value axis — 4 by default, so five labels counting zero.
chartPaddingExtra room around the plot, as a number or { top, right, bottom, left } — added to what the chart works out for itself, so it stays right when the data changes. Negative tightens.
chartValuePositionauto | inside | outside — where showValues puts a bar’s number. auto keeps it outside unless it would overflow, then inverts it to white inside the bar.
Sizes are in the chart’s own coordinate space, not design pixels. A chart is drawn in a canonical box sized by its aspect and mapped onto its layer by the SVG viewBox, so a size means the same share of the chart at any render scale. The video engine names these fields without the chart prefix (fontScale, text, legend, padding…) because a chart asset is already its own namespace there; an image layer’s text and padding are taken.

Shadows, glow and depth

A shadow is cast by what the layer draws, not by its box. A path shape, an icon, a QR and text with no background behind it all cast the shadow of their own outline; a rectangle, a plain photo or a text box with a background casts a box shadow, which is the only one of the two that can take a spread or an inset.

PropertyMeaning
effects.shadow{ enabled, x, y, blur, spread, color, opacity, inset, follow }. Offsets in design px; opacity is 0–100.
effects.shadow.followbox or shape, overriding the automatic choice. Set shape for a cut-out photograph, whose subject lives in the alpha channel and cannot be seen from the layer.
effects.glow{ enabled, blur, color, opacity } — a halo, also cast by the outline
effects.extrudeText only. { enabled, depth, angle, color } — the solid side of a three-dimensional letter. angle is degrees, 90 being straight down; depth is in design px and capped at 80 steps.
An extrude is not a shadow. A shadow is soft and falls away from the letter; an extrude is the letter’s own depth, hard-edged and continuous, which is the device behind most title designs. A layer can carry both, along with a glow — they compose front to back rather than overwriting one another.

QR codes and barcodes

A code layer stores the settings, not a picture of a code — so binding a field to code.value makes one template produce a different, correct code on every render. That is the whole reason to have this rather than an uploaded image: a per-recipient link, an order number, a ticket.

PropertyMeaning
code.typeqrcode, gs1qrcode, datamatrix, azteccode, pdf417, code128, code39, ean13, ean8, upca, itf14, gs1-128
code.valueWhat it encodes. Usually driven by a field.
code.color, code.backgroundHex. A null background leaves it transparent.
code.eccLevelQR only: L, M, Q, H — roughly 7%, 15%, 25% and 30% of the code may be covered and still scan.
code.moduleShape2D codes: square, dot or rounded
code.marginQuiet zone in modules. Defaults to 4, and never goes below 2.
code.showTextLinear barcodes: print the value underneath.
A styled code is still a working code. The modules are drawn from the encoder’s own matrix, and the rounding of both the modules and the finder patterns is bounded so that the centre of every module — the point a decoder samples — stays the colour it was encoded as. The quiet zone has a floor for the same reason: no margin is the most common cause of a code that scans on screen and not off a printed page.

Recolouring artwork that has several colours

Most shapes take one fillColor, and dimensional ones derive their lit and shaded faces from it. Some artwork is multicoloured on purpose — a confetti scatter, a bunting, a two-tone icon — and for those a single fill would only flatten the thing that makes it work.

paletteis an array of colours, positional against the element’s own palette in painting order. An entry that is nullor missing keeps the artwork’s original colour, and anything that is not a valid CSS colour is ignored rather than drawn. Repeated colours count once: a bunting with twelve flags in six colours takes six entries, and changing the second recolours every flag painted in it.

palette
{
  "type": "shape",
  "shapeType": "bunting",
  "palette": ["#8B3DFF", null, "#00C4CC"]
}
The same field recolours a multicolour icon, whose colours are read out of its markup — those icons paint with literal fills instead of currentColor, so fillColor has nothing to tint.

Pictures

PropertyMeaning
imageUriURL or data URI. Usually driven by an image field.
objectFitcover, contain, fill, scale-down, none
frameAny shape id above — masks the picture to that silhouette
adjustmentsbrightness, contrast, saturation (100 is neutral), hue in degrees, plus blur, grayscale, sepia, invert
removeBackgroundCut the subject out at render time — see below
tint, tintOpacity, tintBlendA flat colour laid over the picture

removeBackground is set on the LAYER, not baked into the asset, because the picture usually is not known when the template is authored — a product field supplies a different photo on every render. The cut-out therefore runs on whatever image the render was given.

The first render of each image spends a second or two on the matte; identical images afterwards are served from cache and cost nothing. If a matte cannot run, the render still succeeds with the original picture and says so in warnings — a background that did not come off is a disappointment, not a reason to fail a render you were charged for.

Icons

An icon is stored as geometry, not as a picture. A rasterised icon cannot be recoloured to match a palette and softens the moment it is scaled past its capture size, and those are the two things people do to icons constantly.

PropertyMeaning
iconBodyThe inner SVG markup — paths and shapes only. Sanitised to a strict allowlist on the way in.
iconViewBoxThe source coordinate space, e.g. 0 0 24 24
fillColorTints the whole icon, via currentColor
gradientPaints the icon from a gradient instead of a flat colour
iconRecolourableFalse for multicolour artwork — flags, brand marks — which carries its own colours and ignores fillColor
iconBody is injected into the page the renderer screenshots, so anything supplied through the API is reduced to drawing elements first: no script, foreignObject, use, image, event handlers or external url() references survive. Geometry is all an icon has ever needed to be.

Live screenshots

An image layer can carry screenshot settings instead of a fixed picture. When it does, the renderer captures that page on every render — so a pricing template rendered next quarter shows next quarter’s prices, with nobody reopening the design.

{
  "type": "image",
  "imageUri": "https://cdn.pixbix.app/…/preview.png",
  "screenshot": {
    "enabled": true,
    "url": "https://acme.com/pricing",
    "viewportWidth": 1440,
    "viewportHeight": 900,
    "scale": 2,
    "fullPage": false,
    "blockCookieBanners": true,
    "cacheTtlSec": 3600
  }
}

Binding a field to screenshot.url is the automation case: one template captures whichever page each caller names. imageUri stays on the layer as a preview, and is what the layer falls back to if a capture fails.

RuleWhy
Requires the Screenshot feature on your planA live layer is a capture, and captures are a metered product
Costs one extra credit per layer, per renderPriced the same as any other capture. The estimate endpoint includes it.
At most 4 live layers per templateEach holds a browser slot while its page loads; this bounds what one template can do to the render fleet
cacheTtlSec defaults to 3600A batch of renders of the same page captures it once. Set 0 for genuinely per-render pages.
A failed capture never fails the renderThe layer keeps its preview and the render reports why in `warnings`
A screenshot.url still containing an unfilled {{placeholder}} is skipped rather than fetched, and is not charged for.

Type

PropertyMeaning
fontFamily, fontSize, fontWeight, fontStyleAny Google font; only the cuts the family really ships are loaded
textAlign, lineHeight, letterSpacing, wordSpacingThe usual metrics, in design pixels
textTransformuppercase, lowercase, capitalize
textDecorationunderline, line-through, overline
gradientPaints a gradient and clips it to the glyphs
textHighlightA filled plate behind the text — the badge effect
curveBend the baseline onto a circle of this radius. Positive arcs over the top, negative under the bottom.
numberOfLines, ellipsizeModeClamp to N lines, with a tail ellipsis or a hard cut
richTextPer-word styling — see below
Curved text is drawn as SVG on a path, which is the only way to make the editor and the renderer agree on where each glyph lands. The {{variable}} highlight colour does not apply to a curved run.

Styling one word differently

Everything above sets the type for the WHOLE layer. richText overrides it for a slice of the string — one word in a brand colour, a number twice the size, a phrase in another face — without splitting the sentence across separate layers.

It is an array of runs. Each run carries its own textplus only the properties it changes; everything it does not name is inherited from the layer, so changing the layer’s font later still moves the styled words with it.

One word, its own colour and size
{
  "type": "text",
  "text": "Big Diwali Sale",
  "fontFamily": "Poppins",
  "fontSize": 48,
  "textColor": "#111827",
  "richText": [
    { "text": "Big " },
    { "text": "Diwali", "color": "#E11D48", "fontSize": 72, "fontWeight": "700" },
    { "text": " Sale" }
  ]
}
PropertyMeaning
textThe characters this run covers. Required.
fontFamily, fontSize, fontWeight, fontStyleType, overriding the layer’s
colorThe glyph colour. Named color, not textColor — a run is a span.
textDecoration, textTransform, letterSpacingAs on the layer, for these characters only
backgroundColorA highlighter behind the words, which survives a line wrap
RuleWhat happens
The runs must spell `text` exactlyConcatenated, they have to equal the layer’s `text`. If they do not, the layer renders with its own styling and the runs are ignored — a wrong render is worse than an unstyled one.
A field that replaces the text drops the runsThe runs describe THOSE characters. Substituting a different string means the words they were cut from are gone, so the layer falls back to its own styling.
Variables still work inside a runA run containing `{{price}}` keeps the variable highlight, composed with the run’s own colour.
Omit it for plain textA single run with no overrides is the same as no `richText` at all.
Because the runs are ordinary JSON, this is the supported way to style a word from automation — a workflow can build the array from data it has and the render honours it. To style a word by hand, double-click the text on the canvas and select it.

The canvas

backgroundColor is the floor, backgroundGradient paints over it, and backgroundImage covers both — sized by backgroundFit (cover, contain, fill or tile).

Importing a design#

POST /v1/templates/import turns a finished design from another tool into a template. What you get back depends entirely on what you send, and the difference is worth understanding before you pick an export format.

You sendYou get
SVGText as real text layers — family, size, weight and colour as resolved by a browser — photos as image layers, and everything drawn flattened into one plate behind them.
PNG / JPEG / WebPOne full-bleed image layer. A flat picture has no layers to recover, and nothing can invent them.
HTMLThe same treatment as SVG, for a fragment copied out of a design tool in the browser.
PDF, AI, EPSRasterised at 150 DPI and imported as flat artwork — the first page only. Requires Ghostscript on the server; without it the request explains which export to use instead.
PSDNot supported. Export as PNG for the artwork, or as SVG from Illustrator if the text needs to stay editable.
Only SVG recovers editable text. Everything else — including a PDF or an Illustrator file — arrives as a picture, because recovering type from a page description needs a text-extraction pass this importer does not have. If the words matter, export SVG.

The import is laid out in real headless Chrome rather than parsed, so nested transforms, CSS classes, tspan runs and viewBox scaling all resolve the way they do when you look at the file. The geometry recorded is the geometry a browser computed.

Fonts are matched against Google Fonts and substituted when there is no match — most paid type is not available to serve. Every substitution is reported in the response’s import.fonts and import.warnings, so a design that came in with the wrong face tells you rather than waiting to be noticed.

An import always lands as a private draft with no field schema. Which text a caller may set is a decision about your public contract, and guessing it from whatever strings happened to be in the artwork would make one for you. Declare your fields, give it a main category, then publish it — a draft cannot be rendered.

Status, type and the library#

A template answers three separate questions, and each has its own field. Conflating them is the most common source of a render that is refused for a reason that is not obvious, so they are worth reading once.

FieldQuestionWho decides
statusIs it finished? draft or publishedYou
visibilityWho is it for? private or publicYou
review.stateMay it be listed in the library?pixbix staff

Only published templates render

status is the single gate on rendering. A draft is refused everywhere — by /v1/render, by /v1/generate, by every workflow step and by the dashboard — with TEMPLATE_NOT_PUBLISHED. Publishing is your own decision and takes effect immediately; nobody reviews it, because it only affects renders inside your own workspace.

Before a template can be published it must have a name, a main category, and:

  • For image templates — at least one layer
  • For video templates — a timeline with tracks and a duration
  • No duplicate field keys (duplicates silently drop values at render time)

A public template is copied, never borrowed

You can only render a template your own workspace owns. Marking one public offers it to other accounts to copy — they take their own copy with POST /templates/:id/duplicateand render that. Naming somebody else’s template in a render returns TEMPLATE_NOT_OWNED with a message saying so.

This is deliberate, and it protects both sides. A render is a dependency: if one workspace could render another’s template, the author would break somebody else’s production automation by editing their own design — and the borrower’s version history and plan limits would describe a document they cannot see. Copying makes the dependency explicit and local.

The list and schema endpoints both report canRender so you never have to work this out from status and ownership yourself. When it is false, renderBlockedReason says what to do about it.

Getting into the library

Setting visibility: "public" puts the template in a staff review queue (review.state: "pending"). It appears in the shared library once approved and once it is also published. A public template additionally needs a thumbnail and at least one field — the two things somebody browsing needs in order to judge it.

Editing the design of a listed template sends the listing back for review, so the library cannot be edited into something else after approval. Your own renders keep working the whole time status is untouched, and only the shopfront entry pauses. Going back to private delists it at once and needs no review.

Versioning#

Every change increments version. Each render records the version it used, so a template edited today does not change what a render from last month actually was.

Cache the field schema by version. When a template’s version changes, re-read it — a new required field will otherwise start failing your renders with a validation error.