The complete reference for the MotionDoc v1.0 format. For a gentle walkthrough, read The MotionDoc format first.

Document

{
  "format": "motion-engine",   // always this string
  "version": "1.0",
  "meta":   { "name": "…", "source": "figma", "figmaNodeId": "12:34" },
  "duration": 1.2,             // SECONDS
  "fps": 60,                   // suggested sampling fps (engine is time-based)
  "stage":  { "width": 375, "height": 812, "background": "#FFFFFFFF" },
  "layers": [ /* Layer[], back-to-front */ ]
}

Layer

{
  "id": "12:40",
  "name": "Card",
  "type": "rect",              // rect | ellipse | text | image | vector | group
  "children": [ /* Layer[] */ ],
  "base": {
    "x": 24, "y": 120, "width": 327, "height": 200,   // x/y relative to parent box
    "opacity": 1, "rotation": 0, "scaleX": 1, "scaleY": 1,
    "anchor": { "x": 0.5, "y": 0.5 },                  // transform-origin, 0..1
    "cornerRadius": [12, 12, 12, 12],                  // [tl, tr, br, bl]
    "fill":   { "type": "solid", "color": "#2D6CFFFF" },
    "stroke": { "color": "#00000022", "weight": 1 },   // or null
    "text":   null,                                     // Text for type:"text"
    "image":  null,                                     // data-URL texture
    "shape":  null,                                     // Shape geometry
    "effects":[ /* Effect[] */ ],
    "clip":   true                                      // clip children to box
  },
  "tracks": [ /* Track[] */ ]
}

Paint (fill)

{ "type": "solid",  "color": "#RRGGBBAA" }
{ "type": "linear", "angle": 90, "stops": [ {"pos":0,"color":"#.."}, {"pos":1,"color":"#.."} ] }
{ "type": "image",  "src": "data:image/png;base64,…", "fit": "cover" }

Text

{ "characters": "Hello", "fontSize": 16, "fontFamily": "Inter", "fontWeight": 600,
  "color": "#111111FF", "lineHeight": 1.3, "letterSpacing": 0, "align": "left" }

Shape (native geometry — not rasterized)

{ "kind": "polygon", "points": 3 }
{ "kind": "star", "points": 5, "ratio": 0.4 }
{ "kind": "path", "vw": 780, "vh": 0,
  "paths": [ { "d": "M0 0 L760 0", "stroke": "#FF0000FF", "strokeWidth": 2,
               "cap": "round", "markerEnd": "arrow" } ] }

Effect (effects[])

{ "type": "drop",  "x": 0, "y": 16, "radius": 34, "spread": 0, "color": "#0000002E" }
{ "type": "inner", "x": 0, "y": 2,  "radius": 8,  "spread": 0, "color": "#00000040" }
{ "type": "blur",  "radius": 12 }
{ "type": "bgblur","radius": 12 }

Track

{
  "property": "translateX",          // normalized name (table below)
  "op":  "set",                      // set | offset | scale
  "unit":"px",                       // px | deg | ratio | none
  "base": 0,                         // base value the op combines against
  "keys": [ /* Keyframe[] */ ]
}
opresultmeaning
setvabsolute
offsetbase + vdelta
scalebase * vmultiplier

Property names

Propertyunitapplied as
translateX / translateYpxtransform
translateXY ([x,y])pxtransform
rotationdegtransform
scaleX / scaleYratiotransform
scaleXY ([x,y])ratiotransform
opacitynonestyle
width / heightpxstyle
cornerRadiusTL/TR/BL/BRpxstyle
strokeWeightpxstyle
polygonCountnonestyle (re-derives polygon/star)
trimStart / trimEndratiostyle
fillColornonestyle
Transform properties have a natural base of 0 (translate/rotate) or 1 (scale). Style properties override the box.

Keyframe

{
  "t": 0.0,                          // ABSOLUTE time on the timeline, seconds
  "v": 120,                          // number | [x,y] | "#RRGGBBAA" | bool | string
  "easing": { "type": "cubicBezier", "p": [0.42, 0, 0.58, 1] }
}
A keyframe’s easing governs interpolation from this keyframe to the next. The last keyframe’s easing is ignored. Before the first / after the last keyframe, the value is held.

Easing

{ "type": "linear" }
{ "type": "hold" }                              // step
{ "type": "cubicBezier", "p": [x1,y1,x2,y2] }   // y may overshoot 0..1
{ "type": "spring", "bounce": 0.3 }             // 0 … 1

Value types

Typev
floatnumber
vector[x, y]
color"#RRGGBBAA"
boolboolean
textstring