Features Apograph CMS on GitHub

Project structure

Where everything lives in the monorepo, and which parts are yours to edit.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

Apograph is an Nx monorepo using npm workspaces — not pnpm, not yarn.

apps/
  admin/          React 19 + Vite SPA
  server/         NestJS API — and your content types
  admin-e2e/      Playwright suite for the admin
  server-e2e/     Testcontainer + supertest suite for the API
packages/
  bootstrap/{admin,server}    the two hosts
  database/                   the connection, the outbox, the unit of work
  identity/{domain,server,admin,provider-*}   auth, RBAC, API tokens, SSO
  content/{domain,server,admin,graphql}
  media/{server,admin,provider-*}
  transfer/{domain,server,admin}
  copilot/{domain,server,admin,provider-*}
  cli/                        the `apograph` binary
  create-apograph-app/           the scaffolder

This is the monorepo, not a generated app

An app made with npx create-apograph-app has the same four apps/ and no packages/ — it consumes them from npm. See installation.

How packages are laid out

A package is either flatpackages/design-system — or grouped by domain with a runtime split: packages/content/server, packages/content/admin.

The npm name stays hyphenated regardless of nesting, so packages/bootstrap/admin publishes as @apograph/bootstrap-admin. The root workspaces globs cover both shapes.

A group is not limited to admin and server — it holds however many packages the domain needs, named for what they are. content has four; copilot has six.

Where your code goes

For most projects, the answer is apps/server and nothing else:

PathWhat you put there
apps/server/src/content/collections/One file per collection
apps/server/src/content/pages/One file per single
apps/server/src/content/index.tsThe registered list, plus table re-exports for drizzle-kit
apps/server/src/plugins.tsWhich plugins run, and in what order
apps/server/apograph.config.tsPer-plugin runtime configuration
apps/server/migrations/Generated SQL for your content tables — committed

If you are adding a capability rather than content, you are writing a plugin — see extending Apograph.

Commands

npx nx <typecheck|build|lint|test|serve> <project>
npx nx sync            # after changing cross-project dependencies
npx nx graph           # visualise the project graph

Running the stack

npm run dev

Runs the whole stack in one terminal, as four tasks in their own Nx panes:

TaskWhat it is
server:dev:buildwebpack in watch mode — compile and type errors land here
server:dev:runnode --watch on the built entry point; Nest logs only
admin:devthe Vite dev server, proxying /api to the API
admin:dev:typechecktsc --build --watch, because Vite never typechecks

That last one matters more than it looks. Vite transpiles without type checking, so without a dedicated pane an admin type error simply does not appear until somebody runs a build.

npm run start:server and npm run start:admin still run either half alone.

Database

docker compose up -d                              # Postgres 16
npx nx run server:db:migrate                      # apply every plugin's migrations
npx nx run <plugin>:db:generate --name=<change>   # generate one plugin's migration
npx nx run server:db:studio                       # browse the live database

These nx run targets are the monorepo’s — they are inferred by the @apograph/nx plugin, which is private and never installed in a generated app. An app scaffolded by create-apograph-app runs the same three operations as npx apograph migrate, npx apograph generate and npx apograph studio; see the CLI.

Running several stacks at once

npm run dev is not limited to one checkout. A slot fixes a worktree’s ports and database — slot n is API :300n, admin :420n, database apograph_cms_an — so several branches can each hold a live app.

npm run worktree -- provision <slot> --path <worktree>
npm run worktree -- list
npm run worktree -- release <slot> --yes

Slots share one Postgres container, because CREATE DATABASE already isolates the data and five containers do not fit in memory. Two concurrent stacks is the realistic ceiling on a four-core machine.

Conventions

  • Prettier, four-space indent, single quotes.
  • Admin i18n is react-intl, with each component co-locating its own defineMessages block. There is no shared messages.ts.
  • Authoring conventions are encoded as skills under .agents/skills/server-plugin, admin-plugin, accessibility, admin-e2e, server-e2e, shadcn. They are written for AI agents but are the most precise description of house style that exists.
  • Every directory with project-specific context carries an AGENTS.md, and a CLAUDE.md that imports it. Edit AGENTS.md; never the importer.

The API reference

A running server documents itself. The generated OpenAPI document is served as an interactive Scalar reference:

http://localhost:3000/reference          # the UI
http://localhost:3000/reference/json     # the raw document

It is on outside production. API_DOCS=true publishes it from a deployment; API_DOCS=false turns it off locally.