Content, composed from plugins.
A headless CMS you run yourself — with an AI assistant that asks before it acts.
npx create-orthacms-app - features, all in the MIT core
- 23
- packages on npm
- 71
- protocols: REST, GraphQL and MCP
- 3
- permission keys, checked in the API
- 37
01 — Features
Built in, not bolted on. 23 features in the MIT core. None of them is paid-only.
-
Content library
Nested AND / OR filters that reach through relations, saved as a view. -
Languages
One record per language. Shared fields are written once. -
Version history
Every save is a version. Restoring one adds another. -
Publication protection
Approvals on the current version before it goes live. -
Content alarms
Saved filters that flag records and never block a save. -
Webhooks
Signed deliveries, retried with backoff until they land. -
Roles and permissions
Three roles, every permission checked in the API.
02 — Ortha CMS AI
An assistant that asks first.
It holds your permissions and nothing more. It never publishes — a person presses Publish.
The summary can lose half its words, and the entry has no SEO description yet. I have drafts for both — saving them needs your go-ahead.
content_propose_update {
"id": "e41f0a2b-…",
"values": {
"summary": "Sleeper routes that closed a decade ago are running again across Europe.",
"seoDescription": "Night trains are back: which sleeper routes reopened, and why travellers are choosing them."
}
} Nothing has happened yet — it is waiting for you.
Understood. Nothing was changed.
Enter to send, Shift+Enter for a new line
03 — Architecture
Plugins, not a monolith.
Every capability is a package on the same public contract your own code uses.
04 — Delivery
Three protocols, one API.
The same article, the same token, the same permissions — three ways in.
Request
GET /api/v1/content/article/group/9b2d7c1e?locale=deAuthorization: Bearer orthacms_0TYI4Q…X-Workspace-Id: 3c1e5a… Response
{ "id": "e41f0a2b-…", "status": "published", "locale": "de", "localeGroupId": "9b2d7c1e-…", "publishedAt": "2026-09-20T14:03:11.000Z", "values": { "title": "Die stille Rückkehr der Nachtzüge", "slug": "nachtzuege" }} Request
POST /api/v1/graphql query { article(localeGroupId: "9b2d7c1e-…", locale: "de") { title status author { name } }} Response
{ "data": { "article": { "title": "Die stille Rückkehr der Nachtzüge", "status": "PUBLISHED", "author": { "name": "Mara Lind" } } }} Request
POST /api/v1/mcp { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "content_get", "arguments": { "typeName": "article", "localeGroupId": "9b2d7c1e-…", "locale": "de" } }} Response
{ "jsonrpc": "2.0", "result": { "structuredContent": { "id": "e41f0a2b-…", "status": "published", "values": { "title": "Die stille Rückkehr der Nachtzüge" } } }} 05 — Questions
Asked before adopting it.
Is it really free?
Yes, in production too. Every feature is in the MIT core, with no time limit and no cap on people, records, API calls or bandwidth. A paid plan buys support, not features.
How many people can we add?
As many as you like on Community. After 1.0, the Team plan includes 10 members, then $3 each; Enterprise is unlimited.
Where does our content live?
In your own PostgreSQL database and your own storage. Query it, back it up or move it any time.
Can non-technical people use it?
Yes. Writers, editors and translators do everything in the admin, without touching code.
Can we work in more than one language?
Yes. Translation is built in, and fields that read the same in every language are written once.
Can we require somebody else to approve before publishing?
Yes, per content type: entries need a set number of approvals from someone other than the author before they go live. An edit asks for approval again.
What can the AI assistant see?
Only what the person using it can see. It holds exactly that person’s permissions, and it asks for approval in the conversation before it changes anything.
What do we need to run it?
PostgreSQL 16 and Node.js 22. No search cluster, message broker or cache.
06 — Install
One command. Five choices.
A keyboard wizard picks your adapters. Change any of them later in config.
npx create-orthacms-app