Move a motion design between After Effects, Figma Motion (beta), and Cavalry — layers, transforms, keyframes, and easing intact. Three apps, one JSON file, no network and no account.
⚠️ Figma's Motion API is in beta. This tool depends on it and may break as Figma changes the API. Expect rough edges.
There is no pairwise conversion code. Every app reads and writes the same file, so each new exporter lights up every existing importer for free:
After Effects Figma Motion Cavalry
▲ │ ▲ │ ▲ │
│ ▼ │ ▼ │ ▼
ae-export.jsx figma plugin cavalry-export.js
figma-import.jsx (Import / Export) cavalry-import.js
│ ▲ │ ▲ │ ▲
└─┴──────────▶ motion.json ◀────────────────┴─┘
| Route | Producer | Consumer |
|---|---|---|
| AE → Figma | ae-export.jsx |
Figma plugin → Import |
| Figma → AE | Figma plugin → Export | figma-import.jsx |
| Figma → Cavalry | Figma plugin → Export | cavalry-import.js |
| AE → Cavalry | ae-export.jsx |
cavalry-import.js |
| Cavalry → Figma | cavalry-export.js |
Figma plugin → Import |
| Cavalry → AE | cavalry-export.js |
figma-import.jsx |
| Layers | text, shapes (rect / ellipse / star / polygon / bezier paths), solids, still images |
| Transform | position, scale, rotation, opacity, anchor |
| Hierarchy | AE parenting and precomps ↔ Figma frames and groups |
| Keyframes | position / scale / rotation / opacity, plus path trim (write-on) |
| Easing | full cubic-bézier both ways, per-axis, hold/step, dimension-separated aware |
One file, schema:1, in AE-native units — pixels, top-left origin, Y-down, degrees
clockwise, 0–100 scale and opacity, {influence, speed} easing. It's the format
ae-export.jsx already wrote, so every consumer speaks one language.
All Figma→AE unit conversion happens once, inside the Figma plugin's exporter (opacity 0–1→0–100, scale ×→%, rotation ccw→cw, translation deltas→absolute position, cubic bézier→ influence/speed). The AE and Cavalry importers therefore never need to know Figma exists.
A pleasant side effect: figma-import.jsx can re-import ae-export.jsx's own output, so AE
acts as an oracle for its own importer — which is exactly how tools/roundtrip.jsx tests it.
Copy ae-export.jsx and figma-import.jsx into AE's ScriptUI Panels folder:
- Windows:
C:\Program Files\Adobe\Adobe After Effects <version>\Support Files\Scripts\ScriptUI Panels\ - macOS:
/Applications/Adobe After Effects <version>/Scripts/ScriptUI Panels/
Then Edit ▸ Preferences ▸ Scripting & Expressions → enable Allow Scripts to Write Files and Access Network. Restart AE; both panels appear under the Window menu.
No install? File ▸ Scripts ▸ Run Script File… runs either one ad-hoc.
⚠️ Installing makes a COPY. After pulling changes, re-copy the script or AE keeps running the old one — silently, since a stale exporter still produces perfectly valid JSON. (The ScriptUI Panels folder lives underProgram Files, so the copy needs an admin shell.) Same applies to Cavalry'sScriptsfolder.
- Export: select a comp → Export composition… → save the
.json. Images are copied into a<name>_assetsfolder beside it. - Import: Import JSON… → pick a
.jsonexported from Figma (or from AE).
Plugins ▸ Development ▸ Import plugin from manifest… → pick figma-plugin/manifest.json.
- Import: select the
.json(and optionally the images from_assets— multi-select). - Export: select the top-level frame holding your animation, pick a frame rate, and
click Export JSON. It downloads a
.jsonfor AE or Cavalry.
Keyframes need the Motion (beta) feature enabled on your account. Without it, import still works statically and export reports that there's nothing to read.
Window ▸ Script Editor → open cavalry-import.js or cavalry-export.js → Run. (Or drop
them in Cavalry's Scripts folder to get them under Window ▸ Scripts.)
- Import creates a new composition named after the source and builds into it.
- Export writes the active composition out as JSON.
If you install the scripts into Cavalry's
Scriptsfolder, remember that's a copy — re-copy it after pulling changes, or you'll be running a stale importer.
Cavalry exposes no keyframe curve to scripting (no keyframe getter, and magicEasing isn't a
readable expression), so the exporter recovers the easing by sampling the animation and
fitting a cubic bezier. On a round-trip it reconstructs the original curve to within ~0.3%.
- Springs have no AE/Cavalry equivalent — approximated as a bézier ease, and the exporter warns. Check the timing.
- Text: fonts are matched by family/style and fall back to a default if not installed. Text is placed by its glyph box; a fixed-width text frame may sit a pixel or two off.
- Text animators (per-character/word/line) don't convert — text stays static. See experimental/ for in-progress per-character splitting.
- Gradients: Figma→Cavalry carries real colour stops. AE can't read or write gradient stops from scripting, so anything touching AE degrades to a solid.
- AE→Cavalry anchors: a Cavalry primitive pivots on its geometric centre. Figma always pivots on the centre too, so that route is exact — but an AE layer whose anchor has been moved off-centre will rotate/scale about the wrong point in Cavalry.
- Cavalry→* easing is recovered, not read (Cavalry doesn't expose it). It reconstructs a hand-authored curve to ~0.3%, but a segment only 1–2 frames long has too few samples to fit and falls back to linear.
- Cavalry→* geometry: rectangles, ellipses, polygons, stars and text transfer. A custom
editableShapepath exports as its bounding box (its geometry doesn't come across yet). - Cavalry→* groups: a group becomes a transform-only rig, so a group's own opacity animation doesn't transfer — the exporter warns and tells you to put it on the children.
- Fill/stroke colour animation, Figma auto-layout/constraints/components, masks, blend modes, boolean ops, video footage, 3D, cameras and lights are not transferred.
- AE→Figma shape export still simplifies some multi-group shape layers.
node figma-plugin/test-convert.js # conversion maths, incl. the easing round-trip. No app needed.
node tools/export-test.js # runs the REAL Figma exporter against a captured node tree
node tools/cav-test.js # imports into a LIVE Cavalry and checks the scene back
node tools/cav-export-test.js # Cavalry import -> export -> diff (Cavalry as its own oracle)
pwsh tools/ae.ps1 -mode runfile -file tools/roundtrip.jsx && node tools/compare.js
pwsh tools/ae.ps1 -mode runfile -file tools/ae-scale-check.jsx # the squared-scale regressiontools/ drives the real apps rather than mocking them:
ae.ps1— runs a.jsxin a headless After Effects and returns its value. It picks the newest installed AE:afterfx.comonly drives its ownAfterFX.exe, and mixing versions boots the app but silently never runs the script.roundtrip.jsx+compare.js— builds a fixture comp, exports it, re-imports it, exports again, and diffs. Bounds, transforms, keyframes, easing, and parenting must match.export-test.js— loads the export half offigma-plugin/code.jsand runs it againstfigma-dump.json(a verbatim capture of a live Figma motion file). Tests the shipping code, offline.cav-test.js— Cavalry has no CLI, so this POSTs to theapi.WebServerbridge on:8090to run the importer inside a live Cavalry and read the scene back.
MIT — see LICENSE. Use it, fork it, sell your services around it. No warranty.
If it saves you time, a GitHub Sponsor is appreciated but never expected.