Each entry: the symptom as you see it, the cause in one line, the fix. Exact messages are quoted so you can search for them.
Boot and CLI
Missing required environment variable X.
Cause. A required value is unset or empty — DATABASE_URL, or with mail APP_URL / MAIL_FROM / SMTP_HOST, or a storage backend’s bucket/container.
Fix. Set it in <app root>/.env or the process environment. An empty value counts as unset.
Environment variable X must be a positive whole number (got "…").
Cause. 0, -1, 1e9, 0x20 or text in a numeric setting.
Fix. Use a plain positive integer. For “off” values like deliveryIntervalMs: 0, set it in config/*.ts, not .env.
NODE_ENV is "prod", which this app does not recognise …
Fix. Use exactly production, development or test — or leave it unset locally.
CopilotPlugin is enabled but has no model provider.
Cause. COPILOT_ENABLED=true with neither ANTHROPIC_API_KEY nor COPILOT_OPENAI_BASE_URL set.
Fix. Configure a backend, or set COPILOT_ENABLED=false.
MAIL_PROVIDER must be "smtp" or "console" …
Fix. Use smtp (a generated app accepts only smtp), or leave it empty to send nothing.
A setting in .env changes nothing
Cause. Nothing reads it. An unread variable is not an error. Common cases:
- A generated app does not read
SSO_PROVISION_*,SSO_ALLOW_PASSWORD_LOGIN,SSO_SESSION_TTL_SECONDS,COPILOT_MAX_STEPS& co. — see configuration. - An app generated before 0.5.2 does not read
WEBHOOKS_*— see upgrading. - A variable exported in the shell wins over
.env.
Fix. Read it in the matching apps/server/config/*.ts with the @orthacms/utils-server readers.
No package.json in … or any parent directory — run this inside an Ortha app.
Fix. cd into the app (any subdirectory works) and re-run.
dist/server/src/main.js does not exist — run `orthacms build` first.
Cause. Not built, or rootDir in a generated tsconfig was changed.
Fix. npx orthacms build. Revert any rootDir change.
The API works, the admin is a 404
Cause. Started outside the app root (staticDir is join(process.cwd(), 'dist/admin')), or built with --server only. The boot log warns that no index.html was found.
Fix. WORKDIR / WorkingDirectory= = app root, and a full npx orthacms build.
Field "post.x" has admin.width "…" — use 'half' or 'full'. / names group "…", which "post" does not declare. / Group "post.seo" has no fields
Cause. A content type’s form layout is invalid; checked at define time.
Fix. admin.width is 'half' or 'full'; every admin.group must be a key of the type’s groups, and every group needs at least one field. See content types.
Migrations
relation "users" does not exist on a fresh migrate
Cause. Plugin order: WorkspacesPlugin before IdentityPlugin. Only a clean database breaks.
Fix. Identity before workspaces in plugins.ts; add new plugins at the end.
Migrating plugin "…" failed — applied N of M plugin(s) before it
Cause. One plugin’s migration failed; earlier plugins are committed, later ones untouched.
Fix. Fix the cause, run npx orthacms migrate again — it resumes.
DATABASE_URL is not set — migrating needs to know which database to change
Cause. The CLI loads .env only from the app root.
Fix. Put DATABASE_URL in <app root>/.env or export it in that shell.
No apps/server/drizzle.config.ts in …
Fix. A generated app ships it; restore it — paths resolve from the app root:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'postgresql',
schema: './apps/server/src/content/index.ts',
out: './apps/server/migrations'
});joinTableOf throws, or a save hits a missing table
Cause. drizzle-kit diffs top-level exports only; a type’s table or a many-relation’s join table is not re-exported.
Fix. Run npx orthacms content sync — it rewrites the manifest with every table and join table — then npx orthacms generate. See migrations.
Admin
The login rate limit trips for everyone
Cause. TRUST_PROXY unset behind a proxy — every request has the proxy’s IP.
Fix. TRUST_PROXY=1 (per proxy hop), and make sure the proxy sends X-Forwarded-For.
403 Origin not allowed on sign-in or save
Cause. The admin is served from an origin not in ALLOWED_ORIGINS (default http://localhost:4200).
Fix. ALLOWED_ORIGINS=https://cms.example.com (comma-separate several).
Sign-in “succeeds” and does not stick
Cause. Admin and API on different origins — the SameSite=lax session cookie does not follow.
Fix. Serve both from one origin through one proxy. See deployment.
The admin half-works after an upgrade
Cause. A partial upgrade — two copies of a shared package, two React contexts. Nothing errors.
Fix.
npm ls @orthacms/design-system react # two entries = partial upgradeInstall every @orthacms/* package at one version in one npm install. See upgrading.
Save is missing on a record
Cause. 0.7.0: the record comes from a shared workspace (read-only here), or the type is available only through a shared grant.
Fix. Edit it in its source workspace (Open in source), or grant the type to this workspace as its own.
Public API
401 / 403 / 400 with a token
| Status | Message | Fix |
|---|---|---|
| 401 | Missing `Authorization: Bearer <token>` header. | Send Authorization: Bearer orthacms_… |
| 401 | Invalid API token. | Unknown, revoked or expired. Mint a new one — the secret is shown once |
| 403 | This token’s scope does not allow that operation. | A read token on a write or upload — use a full token |
| 403 | status=draft requires a token with write scope. | ?status=draft or ?status=any needs a full token |
| 403 | This token does not cover that workspace. | X-Workspace-Id is outside the token’s bucket |
| 400 | This token covers N workspaces — name the one you want with the X-Workspace-Id header. | Add X-Workspace-Id |
| 400 | Malformed X-Workspace-Id header. | It must be a uuid |
| 403 | This workspace can only use "Tags" records from shared workspaces; it cannot create its own. | A write to a type held only via a shared grant. Write in the source workspace |
A restricted entry is missing from the API
Cause. The entry’s audiences refuse (or do not admit) the reader. No X-Reader-Tags header = the anonymous reader.
Fix. Send the reader’s tags from your backend:
curl https://cms.example.com/api/v1/content/article \
-H 'Authorization: Bearer orthacms_…' \
-H 'X-Reader-Tags: premium, eu'Renamed the header? Check SEGMENTS_READER_TAGS_HEADER. App generated before 0.7.0? Wire the resolver — see upgrading.
An edited entry still shows the old content
Cause. Expected since 0.7.0. A saved-but-not-republished entry (Modified) keeps serving its published version on REST and GraphQL.
Fix. Publish it.
A localized read returns nothing, or 400 on locale
Cause. No row in that language (there is no fallback on the public API), or the slug is not in config/i18n.ts locales.
Fix. Publish the translation, or read the default locale yourself; use a configured slug.
A field you wrote is not there
Cause. Keys in values that are not declared fields are dropped silently.
Fix. Compare against GET /api/v1/content-types/:name.
Media
curl on /raw prints nothing; a client sees 302
Cause. MEDIA_DIRECT_SERVE=signed-url redirects to the storage backend.
Fix. Follow redirects: curl -L. Do not cache the 302.
Every asset 404s after a redeploy
Cause. MEDIA_LOCAL_ROOT defaulted to ./.storage/media on the container’s disk.
Fix. Mount a persistent volume or use object storage. Old blobs are gone unless the old filesystem survives. See media storage.
A media URL in an img tag is broken in the browser
Cause. The raw route needs the bearer token; browsers do not send it.
Fix. Proxy the bytes through your server, or copy assets to a CDN at build time. See the media API.
Uploads fail with 413 before reaching Ortha
Fix. Raise the proxy’s body limit — nginx client_max_body_size 50m; — to at least MEDIA_MAX_UPLOAD_BYTES.
Webhooks
Use https://. Plain HTTP exposes the payload … or … is a private or reserved address.
Cause. The URL policy: https:// only, no loopback/RFC 1918 targets (checked after DNS, at every send).
Fix (local development only):
WEBHOOKS_ALLOW_INSECURE_URLS=true
WEBHOOKS_ALLOW_PRIVATE_NETWORKS=trueThe literal true; 1 reads as off. Still refused? The app predates 0.5.2 and never reads these — see upgrading.
Read next
- Configuration — every variable
- Errors and status codes — public API errors
- Upgrading — per-release actions