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 value | Layer type |
|---|---|
| 0 | Precomposition (a nested animation) |
| 1 | Solid color |
| 2 | Image |
| 3 | Null (invisible transform-only helper layer) |
| 4 | Shape (vector shapes, fills, strokes) |
| 5 | Text |
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.