---
title: Project layout
description: Where code goes in the monorepo.
---

| Path              | Holds                                                                |
| ----------------- | -------------------------------------------------------------------- |
| `apps/`           | Services and web apps, such as `api`, `web` and `mcp`.               |
| `tools/`          | Developer CLIs that never deploy, such as the demo telemetry seeder. |
| `packages/domain` | Vocabulary and models shared by the browser and the server.          |
| `packages/server` | Code shared by the server apps, one module per directory in `src/`.  |
| `packages/deft`   | The design system: components, theme and tokens.                     |
| `packages/sdk`    | `@openperf/sdk`, published to npm.                                   |
| `apps/docs`       | This site: product, self-hosting, Deft UI and development docs.      |

- Code used by one app lives in that app. Move it to a package once a second app needs it.
- Apps import a server module through its subpath, such as `@openperf/server/ingest`. Modules import each other only through the other module's barrel, never its internals.
- `@openperf/deft` publishes only its `index.ts`. A primitive the apps need goes to Deft with a Deft UI page and an example; a composite bound to app data stays in the app.
- A change to a service, port, environment variable or deploy step updates its [Self-hosting](/self-hosting/overview) page in the same change.
- `@openperf/sdk` is the only package built with `vp pack`. Its entry points are `/node`, `/bun`, `/browser` and `/cli`; `src/core` stays internal. OpenTelemetry versions are pinned in the root `catalog`.
- Every third-party package bundled into the SDK `dist` is listed in `deps.onlyBundle` in `packages/sdk/vite.config.ts`, and its license is in `packages/sdk/THIRD_PARTY_LICENSES.md`, which ships to npm. The build fails on an unlisted package. After adding, removing or bumping an SDK dependency, run the `/update-sdk-licenses` Claude Code skill (`.claude/skills/update-sdk-licenses`); without Claude, build the SDK and run its `generate.sh` from the repository root.
