| Binary | Job |
|---|---|
create-orthacms-app | Scaffolds a new app. |
orthacms | Builds, runs and migrates an app. A devDependency of every generated app. |
Scaffolding an app
npx create-orthacms-app my-cms
# fully non-interactive:
npx create-orthacms-app my-cms --yes --media media-s3 --protocols graphql,mcp --mail mail-smtp| Flag | Values | Default |
|---|---|---|
--yes | Accept every default, ask nothing | — |
--media <id> | media-local, media-s3, media-azure, media-gcs, media-vercel-blob | media-local |
--copilot <ids> | copilot-anthropic, copilot-openai (comma-separated), or none | none |
--sso <ids> | sso-oidc, sso-github, sso-saml (comma-separated), or none | none |
--protocols <ids> | graphql, mcp (comma-separated), or none — REST is always on | REST only |
--mail <id> | mail-smtp or none | none |
--no-install | Skip npm install | — |
--no-git | Skip git init | — |
-h, --help | Usage | — |
--yesor a non-TTY takes defaults without prompting, so CI never hangs.- Ids are the full ids (
media-s3), nots3. - Every
@orthacms/*dependency is pinned to the scaffolder’s own version:npx create-orthacms-app@0.7.0generates a 0.7.0 app. Nothing resolveslatest. - 0.7.0: a generated app installs on npm 10 (Node 22’s bundled npm). Earlier
versions could crash in
npm installwithCannot read properties of null (reading 'edgesOut').
After it finishes:
cd my-cms
docker compose up -d
npm run migrate
npm run dev # → http://localhost:4200The interactive questions
| Question | Kind | Default |
|---|---|---|
| Where should uploads be stored? | one of | Local filesystem |
| AI copilot — which model backends? | any of | none — the copilot stays off |
| Single sign-on — which identity providers? | any of | none — email and password |
| Which protocols should the content API speak? | any of | REST (locked on) |
| How should the app send invitations and password resets? | one of, or none | Do not configure |
Multi-selects default to nothing extra: a hosted copilot sends content to a third party, and an unrequested endpoint is still an endpoint.
Mail. mail-smtp installs @orthacms/mail-server and
@orthacms/mail-provider-smtp, writes config/mail.ts, a plugins.mail field
in orthacms.config.ts, a mailPlugin() helper in plugins.ts, MAIL_* and
SMTP_* keys in .env, and a README section. MAIL_PROVIDER is empty in a
fresh app, so it boots before you have a relay. mail-domain,
mail-provider-console and mail-provider-testkit are installed in every app.
See sending mail.
Content. Every app starts with no content types but with their plumbing:
the empty manifest src/content/index.ts, collections/ and pages/,
drizzle.config.ts, and a baseline migration under apps/server/migrations.
The schema builder is installed and registered
on both sides, with SCHEMA_BUILDER=false in .env.
Building and running
npx orthacms <command> [options]| Command | What it runs | npm script |
|---|---|---|
orthacms dev | tsc --watch, node --watch and Vite together | npm run dev |
orthacms build | tsc for the server, Vite for the admin | npm run build |
orthacms start | The built server, dist/server/src/main.js | npm start |
orthacms migrate | Builds the server, then applies every plugin’s migrations | npm run migrate |
orthacms generate [--name=<n>] | A migration for your content tables, from apps/server/drizzle.config.ts | npm run generate -- --name=<n> |
orthacms content sync | Rewrites the content manifest, apps/server/src/content/index.ts, from the type files beside it | npm run content:sync |
orthacms studio [--host --port] | Drizzle Studio on the app’s database | npm run studio |
| Flag | Applies to |
|---|---|
--server | build, dev — server only |
--admin | build, dev — admin only. Both together is refused |
--host, --port | studio. --port=0 is refused; a non-loopback host warns |
--name=<n> or --name <n> | generate |
-h, --help, help | Anywhere; prints usage, exits 0. A bare orthacms does the same |
-v, --version | The installed version. Wins over --help |
npx orthacms build --server
npx orthacms generate --name=add_article_subtitle
npx orthacms --versioncontent sync writes the manifest
apps/server/src/content/index.ts is generated: the contentTypes array
ContentPlugin registers, plus a top-level export of every table drizzle-kit
must diff — each type’s table, each many-relation’s join table, and the
revisions table. Run content sync after adding, renaming or removing a file
under collections/ or pages/, then generate:
npx orthacms content sync
# content sync: 3 type(s) → apps/server/src/content/index.ts
npx orthacms generate --name=add_recipe- The convention is the whole contract:
collections/<name>.tsexports a collection named<name>,pages/<name>.tsa single. A file that breaks it stops the sync with the reason, rather than being guessed at. - An unchanged manifest is left alone and reported as already up to date.
- It is the one command that loads TypeScript: type files import only
@orthacms/content-server/define, which needs no compile step. - The schema builder renders the manifest with
the same code, so a hand-run sync and an apply write identical files. In the
monorepo the same command is
npx nx run server:content:sync.
migrate compiles first
It builds the server, then reads the compiled config and plugin list — no TypeScript loader at runtime. Migrations run in plugin order, with no transaction across plugins. See migrations.
.env is loaded by the CLI
Nothing else loads it. Variables already exported are not overwritten, so a deployment’s environment wins:
DATABASE_URL=postgres://prod/… npx orthacms migrate # overrides .envThe app's layout is a convention, not configuration
The CLI expects:
dist/server/src/main.js—startdist/server/orthacms.config.js—migratedist/server/src/plugins.js—migrate
These follow from the generated tsconfig rootDirs. Change one and start
reports a missing entry point.
Nothing is bundled
Each plugin finds its migrations relative to its own package in
node_modules. A bundler would break that, so apps compile with tsc and keep
node_modules on disk.
See installation and the database.