Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Features Ortha CMS on GitHub

Project structure

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

Documents 0.13.1 Updated Edit this page Report a problem

On this page

Ortha is an Nx monorepo on 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, SSO
  workspaces/{server,admin}   workspaces, grants, sharing
  content/{domain,server,admin,graphql}
  schema-builder/{domain,server,admin}   the Content Model page
  media/{domain,server,admin,provider-*}
  mail/{domain,server,provider-*}
  copilot/{domain,server,admin,provider-*}
  tools/server                the agent tool registry the copilot and MCP share
  mcp/server                  the Model Context Protocol endpoint
  utils/server                the environment readers (readEnv, readFlag, …)
  cli/                        the `orthacms` binary
  create-orthacms-app/        the scaffolder
  …

This is the monorepo, not a generated app

An app from npx create-orthacms-app has the same four apps/ and no packages/ — it installs them from npm. See installation.

How packages are laid out

  • Flat — packages/design-system — or grouped by domain with a runtime split — packages/content/server, packages/content/admin.
  • The npm name is hyphenated regardless of nesting: packages/bootstrap/admin → @orthacms/bootstrap-admin.
  • A group holds what the domain needs: a framework-free domain kernel, extra protocols (graphql), and provider-* adapters for swappable backends.

Where your code goes

For most projects: apps/server, 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 generated content manifest — rewritten by content:sync or the schema builder, never by hand
apps/server/src/plugins.tsWhich plugins run, and in what order
apps/server/config/*.tsOne module per plugin — the only code that reads the environment
apps/server/orthacms.config.tsAssembles the config/ modules into one typed object
apps/server/migrations/Generated SQL for your content tables — committed

Adding a capability rather than content? Write a plugin — see extending Ortha.

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                          # the whole stack, four Nx panes
npm run start:server                 # API only
npm run start:admin                  # admin only
PaneWhat it is
server:dev:buildwebpack in watch mode — compile and type errors land here
server:dev:runnode --watch on the built entry; Nest logs only
admin:devVite dev server, proxying /api to the API
admin:dev:typechecktsc --build --watch — Vite never typechecks

Database

docker compose up -d                              # Postgres 16
docker compose --profile sso up -d                # + Keycloak on :8080, for SSO work
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
npx nx run server:content:sync                    # regenerate src/content/index.ts

These nx run targets exist only in the monorepo (inferred by the private @orthacms/nx plugin). A generated app runs the same operations as npx orthacms migrate, generate, studio and content sync — see the CLI.

The type files and the manifest can also be written by the schema builder: SCHEMA_BUILDER=true npm run dev, then Content Model in the admin.

Running several stacks at once

A slot fixes a worktree’s ports and database: slot n = API :300n, admin :420n, database orthacms_an.

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

Slots share one Postgres container. Two concurrent stacks is the realistic ceiling on a four-core machine.

Conventions

  • Prettier, four-space indent, single quotes.
  • Admin i18n is react-intl; each component co-locates its own defineMessages block.
  • House style is encoded as skills in .agents/skills/: server-plugin, admin-plugin, accessibility, admin-e2e, server-e2e, shadcn (plus workflow skills ticket, open-pr, agent-browser).
  • Every directory with project context has an AGENTS.md and a CLAUDE.md that imports it. Edit AGENTS.md.

The API reference

http://localhost:3000/reference          # Scalar UI
http://localhost:3000/reference/json     # raw OpenAPI document

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