A headless CMS you assemble from connectors.
Pick a connector for each layer, the API runtime, your database, object storage, cache, and where the admin is hosted. Then run the same code self-hosted, in Docker, or on Cloudflare's edge. Content types are data, so adding a field never needs a migration.
Two simple ideas run through everything.
Every layer is a connector.
The API runtime, your database, object storage, cache, and hosting each sit behind one small interface. Write a connector for your provider and nothing else in the CMS changes. And a connector is only done when it passes the shared test suite, so you never have to trust a README.
Content types are data, not code.
Define and edit content types from the admin while the app runs. Entries live in one table with their fields in a JSON column, validated on the way in. Adding a field needs no migration and no deploy, the database shape never changes.
// one config, one connector per layer. export default defineConfig({ runtime: cloudflareWorkers(), data: supabase({ url: env.SUPABASE_URL }), storage: r2({ bucket: "media" }), cache: kv(env.CACHE), hosting: workersAssets(), }) // swap any line for a different provider. same CMS.
Five layers. Pick a connector for each.
OpenCMS keeps the CMS separate from the infrastructure it runs on. Choose a provider for each layer and watch the config assemble itself, one line per layer. See where it runs →
import { defineConfig } from '@opencms/core' export default defineConfig({runtime: cloudflareWorkers(),data: d1(env.DB),storage: r2({ bucket: "media" }), // plannedcache: kv(env.CACHE), // plannedhosting: workersAssets(),})
Self-host it, containerize it, or ship it to the edge.
The same TypeScript runs in every target. Only the connector bindings change, and the admin ships at the same origin so sessions work with no CORS.
Small at the core, honest about its guarantees.
Migration-free modeling
Content types are data. Add a field with no schema migration and no deploy.
Bring your own backend
Cloudflare, Supabase, PlanetScale, SQLite, or any S3 bucket. One connector per layer.
Conformance-tested
A connector is done only when it passes the shared test suite. Run it yourself.
Auth built in
better-auth handles sessions and API keys. Two roles, and anonymous reads are published-only.
Agent-ready
SoonAn MCP surface so an agent runs the CMS through the same validated services a human uses.
One artifact to ship
API and admin ship at one origin. One deploy carries both, with no CORS to configure.