Self-hosting
Fullbleed is a single Cloudflare Worker. It needs a Cloudflare account, a D1
database, an R2 bucket (media), and — unless you stay on workers.dev — a domain
that is a zone in your account. All state is in D1 and R2; the app itself is stateless.
Prerequisites
- Node.js 20+ and pnpm
- A Cloudflare account with Workers enabled
- A domain zone in Cloudflare (e.g.
example.com) if you want a custom domain - The Wrangler CLI (or
pnpm exec wrangler)
Login once:
pnpm install
pnpm exec wrangler login
1. Configure
Edit wrangler.jsonc:
| Key | What to put |
|---|---|
name |
Your worker name, e.g. fullbleed |
routes[].pattern |
Your custom domain, e.g. cms.example.com with custom_domain: true |
d1_databases[0].database_name |
The name you'll use for the D1 database |
d1_databases[0].database_id |
From wrangler d1 create (step 2) |
r2_buckets[0].bucket_name |
Your R2 bucket name |
vars.APP_URL |
https://cms.example.com (the dashboard's public URL) |
vars.MEDIA_URL |
https://media.example.com (public media URL, step 4) |
vars.PLATFORM_ADMIN_EMAIL |
Email that auto-promotes to platform admin on signup |
2. Create the database and bucket
pnpm exec wrangler d1 create fullbleed
Copy the returned database_id into wrangler.jsonc, then create the media bucket:
pnpm exec wrangler r2 bucket create fullbleed-media
3. Apply migrations
Local (miniflare, used by pnpm dev):
pnpm exec wrangler d1 migrations apply fullbleed --local
Production (the live D1 database):
pnpm exec wrangler d1 migrations apply fullbleed --remote
4. Custom domains (recommended)
Connect the worker domain:
pnpm exec wrangler deploy
Then connect the media bucket so MEDIA_URL actually serves files:
pnpm exec wrangler r2 bucket domain add fullbleed-media --domain media.example.com --zone-id YOUR_ZONE_ID
Get YOUR_ZONE_ID from the Cloudflare dashboard (Domain → your zone → overview).
Using workers.dev alone works for the dashboard, but the API path you hand to
websites is nicer on your own domain.
5. Deploy & verify
pnpm run deploy
pnpm run deployis a full worker deploy. Barepnpm deployis a pnpm built-in and will not deploy the worker.
Check health:
curl https://cms.example.com/api/health
# {"service":"fullbleed-api","status":"ok",…}
6. First signup → platform admin
Sign up at /signup using the email in PLATFORM_ADMIN_EMAIL. That account is
auto-promoted to platform admin and can manage all users at /admin.
7. Local development
pnpm dev # dev server on http://localhost:3001
pnpm build # production build
pnpm start # run the built worker locally (wrangler dev)
Allowed origins (API + CORS)
Browser requests to the public API (GET endpoints) carry an
Origin header. Fullbleed checks it against the workspace's allowed_origins list
and rejects unknown origins with 403 Origin not allowed. Server-to-server requests
without an Origin header skip the check.
Add your site's origin to a workspace (replace the UUID with your workspace id, found
in workspaces.id):
INSERT INTO allowed_origins (workspace_id, origin) VALUES
('<workspace-id>', 'https://yoursite.com');
Deployments with reporting turned on (observability.enabled) surface these queries in
the Cloudflare dashboard's Logs and Metrics tabs.
Architecture
For a deep dive into request flow, auth/sessions, workspace scoping, and the public
API internals, read docs/ARCHITECTURE.md in the repo (developer
documentation, not user docs).