Back to guide
Basics
Advanced

10 min read

Reading Raw Lottie JSON: A Field Guide to the Bodymovin Schema

You don't need to know the Lottie (Bodymovin) JSON schema to use Lottie animations, but it's genuinely useful once you're debugging a broken export, writing a custom optimization script, or trying to understand why a diff in version control is so large. Here's the shape of it, top to bottom.

Top-level document

{
  "v": "5.9.6",     // Bodymovin version that generated this file
  "fr": 30,         // frame rate
  "ip": 0,          // in point (first frame)
  "op": 90,         // out point (last frame) — duration = (op - ip) / fr seconds
  "w": 500,         // width in px
  "h": 500,         // height in px
  "assets": [],     // precomps and embedded/linked images
  "layers": []      // the actual layer stack, last-to-first = bottom-to-top
}

Layer types (the ty field)

ty valueLayer type
0Precomposition (a nested animation)
1Solid color
2Image
3Null (invisible transform-only helper layer)
4Shape (vector shapes, fills, strokes)
5Text

The transform block (ks)

Every layer has a ks ("keyframed shape"/transform) object with the same five properties: p (position), a (anchor point), s (scale), r (rotation), and o (opacity). Each one is either a static value or an animated one:

// static — never changes
"o": { "a": 0, "k": 100 }

// animated — "k" becomes an array of keyframes
"o": {
  "a": 1,
  "k": [
    { "t": 0,  "s": [0] },   // at frame 0, opacity = 0
    { "t": 15, "s": [100] }  // at frame 15, opacity = 100 (fades in)
  ]
}

This is exactly why keyframe-heavy animations produce large files: every animated property on every layer gets its own array of {t, s} objects, and optimizers work largely by simplifying or thinning these arrays without changing the visible motion.

Shapes inside a shape layer

A shape layer's shapes array is itself a small stack of drawing instructions, evaluated like a mini vector-graphics program: "el" (ellipse), "rc" (rectangle), "sh" (arbitrary bezier path), paired with "fl" (fill) and "st" (stroke) items that style whatever shapes came before them, and a "tr" (transform) that positions the whole group.

Why this matters practically

  • A file that "looks simple" but is huge usually has either deeply nested precomps or per-frame baked keyframes (often from an unsupported After Effects expression) — both are visible immediately once you open the JSON.
  • Reviewing a diff between an original and an optimized file makes a lot more sense once you can recognize a thinned-out keyframe array versus an actual visual change.
  • Small hand-edits (recoloring via a fill's color value, nudging a layer's start time) are entirely possible without opening After Effects, as long as you know which field to touch.