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 the handoff-app CLI. It reads and writes the filesystem: component sources, handoff.config.js, and a design-system/ token pipeline. A workspace never deploys itself. It pushes to a registry over HTTP (handoff-app push:all).
  • Registry: a clean, standalone handoff-app deployment (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:

  1. UI: a foundation or component page a human can browse.
  2. REST: a documented OpenAPI route a script or another service can call.
  3. 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

EntityWhat it isDeep dive
TokensDTCG-format design tokens (color, typography, spacing, radius, grid, …), primitive + semantic tiers, multi-axis (brand/scheme)Concepts → Tokens
ComponentsStructured metadata + raw source (code/html/sass/css) + rendered build outputConcepts → Components
PatternsSaved compositions of component blocks (playground pages)Concepts → Patterns
Pages / docsMarkdown documentation pages (distinct from patterns)Concepts → Pages / docs
Design artifactsUploaded/generated design images with an AI-derived component specConcepts → Design artifacts
AssetsIcons, logos, and other brand assets (catalog + search)Concepts → Assets
ChangelogUnified change history across all of the aboveConcepts → 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.

On this page