Architecture
How Handoff actually works, the two run modes, the DATABASE_URL switch, the three-surfaces principle, and the workspace-to-registry pipeline.
The Overview makes the case for why this is worth building on. This page is how it actually works: one application, two modes, three surfaces, and a pipeline that moves data from Figma into everything downstream.
What Handoff 2 is
Handoff is one Next.js application, handoff-app. There is no separate "server" package;
the same codebase runs a developer's local preview, pushes to a deployed registry, and serves
that registry back out over UI, REST, and MCP. It depends on handoff-core for the token
engine: DTCG parsing, alias resolution, and the multi-axis (brand × scheme) token model that
underpins the token pipeline.
The two run modes
Every handoff-app deployment is one of exactly two things:
- Workspace: a client project repo (e.g.
<project>/handoff/), run locally via thehandoff-appCLI. It reads and writes the filesystem: component sources,handoff.config.js, and adesign-system/token pipeline. A workspace never deploys itself. It pushes to a registry over HTTP (handoff-app push:all). - Registry: a clean, standalone
handoff-appdeployment (typically on Vercel) with no client-specific data at deploy time. It stores everything a workspace pushes to it in Postgres, and serves it back out to stakeholders and API/MCP consumers.
The DATABASE_URL switch
Both modes are the same code path, branching on one thing: whether a database connection string is configured at startup.
getDataProvider()
→ DATABASE_URL set → DynamicDataProvider (registry: reads/writes Postgres)
→ DATABASE_URL absent → StaticDataProvider (workspace dev: reads the local filesystem)All page components, REST routes, and MCP tools read through getDataProvider(), never
directly from the filesystem or the database, so the same rendering and business logic works
identically in both modes. See Workspace → Local preview for
what this looks like day to day, and Registry → Model for the full
DataProvider writeup.
Workspace vs. registry auth follows the same switch
MCP and the reference API are unauthenticated in workspace mode (unless
HANDOFF_SYNC_SECRET is set) and require a bearer token in registry mode. See
MCP → Authentication.
The three-surfaces principle
Every canonical data type in Handoff, tokens, components, icons, pages, design artifacts, and so on, is meant to be reachable through three surfaces:
- UI: a foundation or component page a human can browse.
- REST: a documented OpenAPI route a script or another service can call.
- MCP: an agent-callable tool exposing the same data, shaped for a model's context window.
When a new data type is added to Handoff, all three surfaces are expected to grow together. Each
one reads from the same getDataProvider() call, not a separate copy of the data, which is what
keeps them from drifting apart. See MCP for the agent-facing surface, and
Concepts for a per-entity breakdown of all three surfaces side by side.
The canonical data model, at a glance
| Entity | What it is | Deep dive |
|---|---|---|
| Tokens | DTCG-format design tokens (color, typography, spacing, radius, grid, …), primitive + semantic tiers, multi-axis (brand/scheme) | Concepts → Tokens |
| Components | Structured metadata + raw source (code/html/sass/css) + rendered build output | Concepts → Components |
| Patterns | Saved compositions of component blocks (playground pages) | Concepts → Patterns |
| Pages / docs | Markdown documentation pages (distinct from patterns) | Concepts → Pages / docs |
| Design artifacts | Uploaded/generated design images with an AI-derived component spec | Concepts → Design artifacts |
| Assets | Icons, logos, and other brand assets (catalog + search) | Concepts → Assets |
| Changelog | Unified change history across all of the above | Concepts → Changelog / audit |
This table is the shape, not the full reference. Concepts covers what each entity actually looks like in Postgres and which UI/REST/MCP routes touch it.
The workspace → registry pipeline
Putting the two modes and three surfaces together, here's the full path a piece of data takes from Figma to an agent's context window:
Concretely: handoff-app fetch pulls raw tokens out of Figma into the workspace filesystem.
npm run tokens:build runs those tokens through handoff-core's DTCG pipeline into compiled
CSS/SCSS/Tailwind/DTCG output. handoff-app push:all then sends everything, tokens, components,
pages, icons, logos, config, up to the registry over POST /api/registry/* and
POST /api/sync/upload, where it lands in Postgres. From there, every read, a foundation page
rendering, an OpenAPI route responding, an MCP tool answering an agent, goes through the same
getDataProvider() call described above. Nothing downstream of the registry ever touches the
workspace filesystem again.
See Workspace → Directory conventions for what lives on the left side of that diagram, and API → Registry cluster / API → Sync cluster for the push endpoints in the middle.
Where to go next
- Workspace: installing the CLI,
handoff.config.*, directory conventions, the token pipeline, local preview. - Registry: deploying a registry, first-admin setup, env vars, the stakeholder-facing UI.
- API: the REST surface: OpenAPI spec, auth & scopes, and each route cluster.
- MCP: the Model Context Protocol server: tool catalog, auth, onboarding.
- Concepts: one page per canonical data type, showing its UI + REST + MCP surface together.