Managed content infrastructure. Branch-native, inheritance-first.
Coho CMS · managed · API-first · in alpha
Coho is a hosted content platform your team drives through APIs, a CLI and SDKs. Content and its schema branch together, so a new field and its forty pages ship as one reviewed release. Type inheritance is coming.
coho diff v0.0.x1 type · 40 entries
then coho promote prod v1.4.0one release
Each generation of CMS solved one problem and left another behind.
Coho keeps what each generation got right. It is built around the job none of them made central: taking a change from the people who author it, through the people who engineer it, to a release that ships whole.
- First · Integrated CMS
The CMS was the website.
- Got right
- Editors saw the real page as they worked.
- Left behind
- Content, templates and code shared one system, so publishing meant editing the live site.
- Then · Enterprise platforms
Structure and governance.
- Got right
- Typed templates that inherit from each other, and workflow for sign-off.
- Left behind
- They took specialists to run, and a change to a base template could reach every page at once.
- Later · Headless CMS
Content as data.
- Got right
- An API in front of the content, so any frontend can use it.
- Left behind
- The schema moved into code and migration scripts, environments became copies, and publishing went entry by entry.
- Now · Coho
Content with a release process.
AuthoringEngineeringRelease management
One workflow for all three, with an opinionated process the API enforces. A stale edit, an invalid merge or an unbuilt release is refused before it costs you, so shipping becomes routine.
The lineFour claims, each backed by a section below.
- BuiltManagedWe host and operate it, from sign-in to the delivery pools your site reads from. Your team runs no servers, databases or upgrades.06 · Delivery
- Builtcontent infrastructureAn API-first platform. Everything the portal does is an API call, so your scripts, CI and SDKs can do it too.07 · 08 · History and tooling
- BuiltBranch-nativeContent and its schema branch, diff, merge and release together. The branch is how work gets done, and it costs the same at any size.01 to 05 · The workflow
- Cominginheritance-firstContent types that extend a base type, with every change to the base reviewed on a branch, along with everything it touches, before it lands.Next · designed, not built
A schema change and its content ship as one release.
Content types are data in the project, versioned on the same branches as the entries they describe. Branch feature/redesign, add a heroVariant field to landingPage, and update the forty landing pages that use it. The trunk and every environment stay exactly as they were.
Preview the branch, review one diff that holds the type change and all forty entries, then merge, tag and promote it. If it goes wrong, prod returns to the previous release in one call, schema and content together.
- O(1)A branch copies nothing, so it costs the same at any size. Measured p50 on Aurora PostgreSQL is 1.7 ms at 100,000 entries and 4.3 ms at 250,000.
- validWrites validate against the type as it stands on their branch. A merge re-validates what it touches against the target's schema, so an entry that no longer fits stops the merge.
- roleBranching needs
branch:create, which every author holds.
coho diff v0.0.x1 type · 40 entries · one reviewThree-dot by default. The diff lists what changed on your branch since it split from v0.0.x, the type and its entries in one list, and previews the merge with any conflicts.
Trunk v0.0.x and every environment are untouched while the branch changes. Sequence numbers are illustrative, from the sample account acme.
A model change goes out first as a migration script, and the pages that depend on it follow in a second publish.
The type change and its forty pages are one diff, one merge and one release, and they roll back together.
Parallel edits merge field by field, locale by locale.
Editors, translators and developers each work on a branch. A merge compares source, target and their common base one field and one locale at a time, and matches repeater items by IDs the server assigns, so edits to different parts of one page never collide.
It stops for a person only where both sides changed the same thing. Each conflict is answered by naming a side, and the merge lands whole or not at all.
- previewThe diff previews the merge, conflicts included, so nobody has to attempt one to find out.
- wholeEvery change lands in one transaction, or the merge is refused and nothing moves.
- tokenWith
--reviewed, a merge is refused withMERGE_REFS_MOVEDif anything changed since you looked.
coho merge --into v0.0.x -m "Redesign" --reviewedlands whole or not at allThree people, one entry, one real conflict. The merge preview for landing/pricing from feature/redesign into v0.0.x.
| Path | Base | Branch | Trunk | Merge |
|---|---|---|---|---|
| title.en-US | Pricing | Pricing 2026 | Our pricing | needs a side |
| title.de-DE | Preise | Preise | Preise 2026 | takes trunk |
| sections[a41f].headline | Plans | Plans | Our plans | takes trunk |
| sections[c07e].body | Three plans… | Two plans… | Three plans… | takes branch |
| heroVariant | absent | split | absent | takes branch |
Four paths merge on their own. One needs a person, and nothing is written until it has an answer.
Two people on one page take turns, or one copies the other's changes across by hand.
Each works on a branch, and the merge asks only about the fields both of them changed.
Review the real site on any branch.
The Runtime API takes the ref in the path, so every branch, tag and environment has a URL. Point a preview deployment at feature/redesign and editors review the actual site, rendered by your production code, before anything merges. Preview and production are one contract with two implementations, and a test asserts that both return byte-identical bodies.
GET https://delivery.coho.example/v1/projects/{project}/{ref}/entries/{id}?locale=en-US Authorization: Bearer <delivery key>
GET /api/v1/accounts/{account}/projects/{project}/preview/{ref}/entries/{id}one contract · two implementationsbyte-identical bodies, assertedA ref in the path
- Preview deployments, per-branch CDN cache keys and per-branch tokens follow from the URL.
- Each branch gets its own preview by changing one path segment.
- Promoting a release repoints an environment, so your frontend's URLs never change.
Fast on deep branches
p99 for a 50-entry listing on a branch two to five levels deep, once the branch's index is built.
Measured on Aurora Serverless v2 with 100,000 entries. The index builds in about five seconds after branching, and until then the same read takes about 350 ms. How we measure
Locales
- A response carries one locale. A field with no value in that locale is left out, so a preview shows exactly what still needs translating.
GET …/entries/hello-world?locale=de-DE { "rank": 1 } title has no de-DE value, so it is absent
Illustrative response for the quickstart entry.
What editors approve is served by different code than what ships, and the two can drift apart.
Every branch is served through the production contract, and a test holds the bodies byte-identical.
What QA signed off is exactly what prod serves.
A tag stamps an immutable, undeletable release, and its snapshot is built once. Promoting repoints an environment at that release, so qa and prod read the same artifact, byte for byte.
Tagging and promoting to each tier are separate permissions, so the person who writes a change need not be the person who ships it. On a small team, one person can hold every role.
coho tag create v1.4.0 --branch v0.0.x -m "Redesign"can run · release:tagstamps (v0.0.x, seq 318)immutableundeletableillustrative
A maintainer can tag v1.4.0 and promote it to qa.
maintainer adds merge, release:tag, promote:dev and promote:qa · prod needs promote:prod
coho promote qa v1.4.0can run · promote:qacoho promote prod v1.4.0 -m "Go live"requires promote:prodPromotion repoints an environment at a tag. The first promotion of a tag may answer SNAPSHOT_NOT_READY while the artifact builds, and the CLI waits.
Production is whatever each entry says at the moment it goes live, and nobody signed off on the whole.
Production serves a release, reviewed as one set and promoted by someone who holds promote:prod.
The tenth environment costs one row.
An environment is a pointer at a release. Ten environments on one release share a single build, so adding one costs a database row, and environments never appear on the bill.
Add the tiers your process needs, such as a second QA lane or a regulated UAT gate, and promotion permissions follow each tier. Serve prod-us and prod-eu from the same release.
- grantGrants attach to tiers, so a new environment in an existing tier needs no role change.
- auditEvery promotion and rollback is recorded with who made it, when and why.
- skipPromoting past a tier is allowed with a
SKIPPED_TIERwarning, so a hotfix can go straight to prod. The permission ladder is the gate.
coho tier create qa2 --resolves snapshot --ord 15no deploy · no plan changeRollback is one call · illustrative
- you
coho promote prod v1.4.0prod points at v1.4.0 - teammate
coho promote prod v1.4.1prod points at v1.4.1, fixed forward - you
coho rollback prodNot run yet. Press Roll back prod.
Tiers in this project
devlive · follows a branch tipqasnapshotstagesnapshotprodsnapshot · prod-us and prod-eu on one releaseqa2custom tier, added by you
Creating a tier is an owner's call (workflow:write). Whoever holds promote:qa2 may promote there.
Each one duplicates your content, so plans cap them and teams queue for the few they have.
An environment is a pointer, so add one for each gate, lane or region, at no charge.
Inheritance you can review before it lands.
When content types cannot extend each other, the same six SEO fields get copied into fourteen types, a reference field lists nine types by name, and a query for everything publishable names each type by hand.
Coho's types are designed to extend a base type. A blogPost is an article, a query for articles returns every kind, and a change to the base arrives as a diff on a branch that counts every type and entry it touches, to merge or revert as one unit.
- oneOne parent per type, plus reusable field groups for what types share.
- safeA subtype can add or tighten fields and cannot remove or loosen what it inherits, so anything true of an article holds for a blog post.
- todayType responses are documented as declared, with
?resolve=effectivereserved for the expanded form, so what you build now keeps working.
designed · not builtcomingFor a team leaving a template-based platform, it also means bringing a type hierarchy across intact, instead of flattening it into copied fields.
IllustrativeEach box is a content type, and a descendant keeps every promise its parent makes. A change to page would reach the five types below it, and it would arrive as one diff on a branch.
Flat types
Shared fields are copied into each type, and changing them means one migration per type.
Inheritance without branches
A change to a base template goes live on every descendant at once.
Coho, coming
A change to a base type is a branch diff that counts what it touches, merged or reverted as one unit.
Content delivery is isolated from authoring, for speed and resilience.
We host and operate every part of Coho, from sign-in to the delivery pools your site reads from. Your team runs no servers, databases or upgrades.
Every release is built once into an immutable artifact, and delivery serves your site straight from it, on its own tier with no database behind it. Nothing from authoring sits on the request path, so imports, merges and builds never slow the pages your visitors load.
- warmPromotion only points an environment at a release that is already built, and delivery keeps the releases in use warm, so a switch never waits on a build.
- cardIf a payment fails, editing pauses and your published site keeps serving.
- exportAny release exports byte-exact, as NDJSON or a tar archive.
v1.4.0 · (v0.0.x, seq 318)built once · read by every environmentWhat crosses the gap is the release itself. It is built once on the authoring side, and delivery only ever reads it.
Targets
Snapshot p99 target. Public and CDN-frontable.
Live p99 target. Private, behind the BFF.
Service-level targets from the contract. On a tag, the ETag is the release's content hash, a cache key that never goes stale. How we measure
Isolation
- Free projects run on their own pools, so free-tier churn never costs a paying customer.
- Enterprise gets a delivery pool of its own.
- The authoring API is private. Only the BFF, which holds sessions and sign-in, faces the internet.
Delivery keys
coho key create --label site-prod --ref prodshows the key once. Only its hash is stored.- Keys can be limited to named refs and an expiry. Revocation takes effect within a few seconds.
coho key public prodopens a named environment to keyless reads. A tag addressed directly still needs a key.
Page views query the database your editors write to, so a bulk import or a busy editing day slows your site.
Delivery reads finished releases on its own tier, so nothing your editors do slows your site.
Every write is a version.
Content keeps the kind of history code keeps. Edits never overwrite each other silently, and nothing is physically deleted.
Writes
ETag: <version id>Every write creates a version, and its ETag is the version id.
If-Match · VERSION_CONFLICTUpdates and deletes send
If-Match. A stale write fails with a conflict and the other edit stays.coho entry history IDA delete is a tombstone with its own ETag. Full entry history includes it.
Schema
"omitted": trueRetire a field in two phases. Mark it omitted, which is reversible, and remove it later.
DESTRUCTIVE_SCHEMA_CHANGERemoving a field, narrowing a type or dropping a locale is refused unless you pass
--confirm-destructive.validated on the refWrites validate against the type as resolved on that ref.
Content infrastructure, driven like the rest of your stack.
Eight factscohoThe CLI, a cross between git and aws. Sticky context, profiles, --output json for CI.
coho-management-sdkTyped Python SDK. Passes mypy --strict on Python 3.11+.
delivery sdksRead-only clients for Java with Kotlin bindings, Node, Python, .NET and Go.
PlannedopenapiHand-written specs. The build fails when routes and spec drift.
error.codeEvery error carries a stable code to branch on.
pkceBrowser login, tokens in the OS keyring. The CLI never sees an authoring credential.
404A project you have no role in answers 404.
Apache-2.0CLI and SDK, on github.com/coho-cms.
One shared line, then a lane for each job.
Log in once and pick an account. Then model and write content in the authoring lane, and release it in the lane below. Every command below is the verbatim quickstart.
Install the CLI with the instructions in the coho-cli repository.
acme is the labelled sample account used across this site.
The shared line
- 01
coho loginPKCE in the browser. The token goes to the OS keyring. - 02
coho use acmeSets the sticky context. acme is a sample account.
I model and write content
- 03
coho project create "Marketing site" - 04
coho type put blogPost --file blogPost.json
- 05
coho entry create -t blogPost -s hello-world --set title.en-US="Hello, world" --set rank=1 - 06
coho branch create feature/pricing --use
I ship releases
- 07
coho diff v0.0.x - 08
coho merge --into v0.0.x -m "New pricing copy" --reviewed - 09
coho tag create v1.0.0 -m "First release" - 10
coho promote qa v1.0.0 - 11
coho promote prod v1.0.0 -m "Go live" - 12
coho rollback prod
I build the frontend
- 13
coho key create --label site-prod --ref prodThe key is shown once. Send it as a Bearer header. - 14
coho invite create jane@acme.example --role memberInvite a teammate to the sample account.
On a small team one person holds every role. Run the authoring lane, then the release lane.
Field notes
Essays and guides sit above the gap. Releases sit below it, one per tag, each with the coordinate it stamps.
Why a schema change should ship with its content
coho diff v0.0.x8 min readMerging field by field, locale by locale
title.de-DE7 min readThe tenth environment costs one row
coho tier create qa26 min readInheritance you can review before it lands
Designed, not built9 min readRelease cards come from the sample account acme and are illustrative. Tags carry a sequence number, so the shelf orders them by seq.
We will never charge for good practice.
Everything your team needs to work safely is free on every plan. You pay for what costs money to run, at rates worked out from what it costs, and every price will be published, Enterprise included.
Proposed, not final. Base fees, seats and single sign-on are still open.
The tools of good practice.
- Branches
- Environments
- Tags
- Diffs and merge previews
- Merges
- Promotions
- Rollbacks
Each one is a pointer or a row, so it costs almost nothing to run. Charging for them would push teams to share one staging environment, skip a review or edit production directly.
What costs money to run.
- Delivery, per GB served
- Active releases, per GB-month
- History beyond five times your content
A release costs nothing to tag or keep. You pay for the ones your environments are serving, by size, and environments that share a release share its cost. Every rate is worked out from what it costs to run.
The one limit on the free side is live previews. They read unfinished branches directly, which costs real compute, so each plan includes a set number.
| Plan | Freehard limits · no card | Siteper project · fee plus usage | Enterpriseplatform fee plus usage |
|---|---|---|---|
| Monthly feeper project on Site | $0 | Openflexible | Openflexible |
| Authoring | |||
| Active releasesthe releases your environments serve, by size | 10 MBacross active releases | 100 MB includedthen $5 per 100 MB | Site rateno allowance |
| Historyevery version is kept | 5× content | 5× content includedthen $3.50 per GB-month | Site rateno allowance |
| Live previewsbranches on a live tier | 1 | 2 | Openflexible |
| Editorsseats | Every plan includes editors. How seats are counted and priced is still open.flexible | ||
| Single sign-onyour identity provider | Planned. Which plans include it, and on what terms, is still open.flexible | ||
| Never chargedBranches, environments, tags, diffs, merges, promotions and rollbacks cost nothing on any plan. | |||
| Delivery | |||
| Deliveryper response, 16 KB minimum | 2 GBabout 125,000 requests | 10 GB includedthen $0.85 per GB | Site rateno allowance |
| Delivery pool | Free-tier poolkept apart from paying sites | Shared | Your ownwith an SLA and priority support |
Alerts come at 80% and 100% of every allowance, and going over never takes a paying site down. Prices belong to a plan version, so a price change is a new version and existing accounts keep theirs.
See the full pricing pageTalk to us about volume