Design-to-code in CI
Problem: You have a canonical Moda canvas that represents your design tokens (colors, fonts, radii, spacing variables). On every push tomain, regenerate tailwind.theme.ts (or equivalent) from the canvas and commit the diff, so code always tracks design.
Primitives
GET /v1/canvases/{id}/tokens— structured JSON of colors / fonts / radii / variables- A tiny codegen step in your CI (Node / Python / Deno)
- Commit + PR if the diff is non-empty
designs:read only. Note that a key created in Settings → Developer → REST API has no scope picker — it carries the default grant (every scope except admin), so the CI key is a full-privilege credential even though this recipe only reads tokens. Give CI its own key, store it as a repository secret, and revoke it independently.
TypeScript — GitHub Actions
.github/workflows/sync-theme.yml:
scripts/sync-theme.mjs:
Python — GitLab CI variant
.gitlab-ci.yml:
scripts/sync_theme.py:
Why tokens, not get_canvas
GET /v1/canvases/{id}/tokens is a dedicated endpoint that returns structured JSON. Parsing tokens out of the pseudo-HTML from GET /v1/canvases/{id} works but is fragile — layer names / HTML shape can change without signaling a token change. Use the dedicated endpoint for CI.
Making it diffable
- Sort all arrays before emitting — otherwise insertion order in the canvas creates spurious diffs on every run.
- Emit a deterministic timestamp header (or omit the timestamp entirely; git tells you when the file changed).
- Name variables in the Moda canvas — named variables land as keys in the
variablesobject and become yourcolors.primary,colors.background, etc.
Gotchas
- Treat the CI key as full-privilege. This recipe only uses
designs:read, but a Settings-created key carries the default grant — a leak is not limited to token reads. Isolate by key (one per integration) and rotate on any suspicion. - Unknown response fields may appear over time. Don’t fail the build if the JSON has new keys — only fail if the keys you need are missing.
- Cache-bust properly. If your codegen reads other files (a base theme, palette overrides), include them in the cache key for the CI action.
- Don’t bypass PR review by committing directly to
main. PR the change — design tokens can have visual fallout. - The canvas must be team-accessible to the API key’s team. Share links do NOT grant design token reads in CI — use a canvas URL + a team-scoped key.
See also
../references/canvases-and-exports.md— tokens endpoint reference../references/authentication.md— scopesmoda-mcp/references/gotchas.md— its Design-to-code section, the IDE-side story for turning a canvas into code