0xCMS is an edge-native content management system that runs entirely on Cloudflare Workers. OAuth 2.1 sign-in, publishing that fans out to any target — database, static JSON, IPFS — versioned content, private media vault. Zero servers, zero cold starts.
❯ npm run deploy
Compiled Tailwind admin UI ✓
Uploaded 0xcms (2.41 sec)
D1 bindings: DB · PUBLISHED_DB
R2 vault: MEDIA_BUCKET [private]
Publish targets: d1 · r2 · plugin:ipfs
Durable Object: PageSyncDO [realtime]
✔ Deployed to https://cms.eventuai.com
❯
// 01 — introduction
Everything a serious publishing stack needs, compiled into a single Worker. No origin servers. No databases to babysit. Trustless by default — even your media bucket is private.
Sign in with Eventuai, GitHub, or Google. Dual JWTs — 15-minute access tokens plus rotatable 7-day refresh tokens, hashed in D1 for instant revocation.
Publishing fans one snapshot out through adapters: the published D1 store, static JSON in an R2 bucket, or any plugin Worker — pin to IPFS, ping a webhook, feed a search index. Per-target failures surface in the editor.
Every save mints a new page version. Roll back any page to any point in its history — your content ledger is append-only.
A Durable Object coordinates concurrent editors per page, so your team never overwrites each other's work.
Uploads land in a private R2 bucket — never public. The Worker gates every read at /media/* with on-the-fly image resizing for previews.
Extend with separate Workers over service bindings: lifecycle hooks, custom content types, fields & blocks, admin pages, publish targets. No runtime installs — just bind and redeploy.
// 02 — architecture
Drafts stay private. One publish action mints a snapshot and fans it out through adapters — to the published database, a static-JSON bucket, or any plugin Worker you bind. Readers never touch your admin data.
PUBLISH_TARGETS = "d1,r2" · plugins opt in with publishTarget: true · per-target failures reported, never silent
// 03 — get started
From clone to edge deployment in nine steps. All you need is a Cloudflare account and wrangler.
$ npm install
Two isolated stores: cms for private admin data, cms-published for live content only. Copy the printed database_id values into wrangler.toml.
$ npx wrangler d1 create cms
$ npx wrangler d1 create cms-published
Add --remote for production. Locally, npm run db:migrate applies both.
$ npx wrangler d1 migrations apply cms
$ npx wrangler d1 migrations apply cms-published
The bucket stays private; the Worker serves objects at /media/<key>. The MEDIA_BUCKET binding ships in wrangler.toml.
$ npx wrangler r2 bucket create worker-cms-media
⚠️ The JWT secret must be at least 32 characters — the Worker refuses to serve in production with a weaker key. Create .dev.vars for local dev.
# random 32-byte signing key
$ openssl rand -hex 32 | npx wrangler secret put JWT_SECRET
Pick your gateways in wrangler.toml, then add a client ID + secret per provider.
ENABLED_PROVIDERS = "eventuai,github,google"
GITHUB_CLIENT_ID = "<client-id>"
$ npx wrangler secret put GITHUB_CLIENT_SECRET
Sign in once, then promote your account. Roles are a comma-separated list.
$ npx wrangler d1 execute cms --remote \
--command "UPDATE users SET role='admin' WHERE email='[email protected]'"
$ npm run dev
# → http://localhost:8787
$ npm run deploy
Publishing defaults to the d1 target. Add r2 for static JSON snapshots in a bucket:
$ npx wrangler r2 bucket create worker-cms-published
# wrangler.toml — bind the bucket, then enable the target
[[r2_buckets]]
binding = "PUBLISH_BUCKET"
bucket_name = "worker-cms-published"
[vars]
PUBLISH_TARGETS = "d1,r2"
For everything else, publish targets are plugin Workers. Two are ready to deploy —
plugin-publish-ipfs
(pins pages to IPFS via Pinata) and
plugin-publish-webhook
(HMAC-signed webhooks for search indexers, rebuilds, deploy hooks) — or write your own: any Worker whose manifest declares publishTarget: true.
# deploy the plugin Worker, share the CMS secret with it
$ git clone https://github.com/zeroxcms/plugin-publish-ipfs && cd plugin-publish-ipfs
$ npm install && npx wrangler secret put PLUGIN_SECRET
$ npx wrangler deploy
# CMS wrangler.toml — bind it; no PUBLISH_TARGETS change needed
[[services]]
binding = "PLUGIN_PUBLISH_IPFS"
service = "cms-plugin-publish-ipfs"
[vars]
PLUGINS = "PLUGIN_PUBLISH_IPFS"
// ready when you are
The live instance is running on the edge right now.
Launch 0xCMS ↗cms.eventuai.com