# Emdash CMS Cloudflare Free Tier Production Playbook

> Deploy EmDash on Cloudflare Free plan with a reliable path, clear feature boundaries, and practical rollback guidance.

Source: https://emdashcmseverything.com/docs/emdash-cms-cloudflare-free-tier-production-playbook/

- Published: 2026-04-08

## Executive summary

You can run EmDash in production on Cloudflare Free plan for core CMS workflows.
What you must accept: sandboxed plugin execution is unavailable, so `worker_loaders` must be removed from configuration.

This guide is optimized for stability first, not maximum feature surface.

## Capability boundaries on Free plan

### Available

- EmDash core site and admin flows
- D1-backed content data
- R2-backed media storage (within free usage)
- Workers deployment and routing

### Not available

- Dynamic Workers for sandboxed plugins
- plugin marketplace-style untrusted runtime execution

If your launch depends on sandboxed third-party plugin execution, Free plan is not sufficient.

## Pre-deployment checklist

Complete all checks before running deployment commands:

- Wrangler is authenticated (`wrangler login` already completed)
- project builds locally without cloud-only assumptions
- `wrangler.jsonc` has D1 and R2 bindings aligned with runtime config
- `worker_loaders` entry is removed for Free plan
- package manager choice is consistent (`npm` or `pnpm`, no mixing)

## Resource provisioning order

Provision resources in this exact order to reduce binding churn:

1. Create D1 database
2. Create or enable R2 and then create bucket
3. Update `wrangler.jsonc` with exact names and IDs
4. Build and deploy Worker

Reason: D1 ID and bucket names become your source of truth for environment bindings.

```bash
# Provision D1 first
npx wrangler d1 create your-db-name

# Then provision R2 bucket
npx wrangler r2 bucket create your-media-bucket
```

## Minimal `wrangler.jsonc` shape for Free plan

The following skeleton is intentionally small:

```jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "your-site-name",
  "main": "./src/worker.ts",
  "compatibility_date": "2026-04-08",
  "compatibility_flags": ["nodejs_compat"],
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "your-db-name",
      "database_id": "replace-with-d1-id"
    }
  ],
  "r2_buckets": [
    {
      "binding": "MEDIA",
      "bucket_name": "your-media-bucket"
    }
  ]
}
```

No `worker_loaders` section on Free plan.

## Build and deploy flow

Run commands from your EmDash project root:

```bash
# choose one package manager and stick to it
npm run build
npm run deploy
```

If your project is pnpm-based:

```bash
pnpm build
pnpm deploy
```

Avoid cross-running `npm` and `pnpm` in the same deployment session unless lockfiles are intentionally synchronized.

## Post-deploy validation checklist

Validate in this order:

1. Frontend URL resolves and returns expected content
2. Admin URL (`/_emdash/admin`) is reachable
3. first admin setup completes successfully
4. create and publish one test item
5. upload one media file and confirm retrieval

A deployment is not complete until data write paths are verified, not just route reachability.

## High-frequency failure modes and shortest fixes

### Failure: deployment succeeds, admin fails to initialize

Likely cause: binding mismatch between runtime integration and `wrangler.jsonc`.

Fix path:

1. verify binding names (`DB`, `MEDIA`) are identical everywhere
2. redeploy after correcting config
3. retry admin initialization

### Failure: R2 command blocked by account prompt

Likely cause: R2 was not enabled in dashboard yet.

Fix path:

1. enable R2 in Cloudflare dashboard
2. accept billing terms (free usage still applies)
3. recreate bucket command

### Failure: plugin-related runtime error on Free plan

Likely cause: leftover sandbox configuration.

Fix path:

1. remove `worker_loaders`
2. disable sandbox-specific plugin setup
3. redeploy and retest

## Rollback strategy

Use a conservative rollback policy:

- keep last known good deploy commit tagged
- if production regression appears, redeploy previous tagged commit
- postpone schema or plugin changes until traffic stabilizes

Operationally, fast rollback beats deep hotfix during incident windows.

## When to upgrade from Free to Paid

Upgrade only when one of these is true:

- you need sandboxed untrusted plugin execution
- your traffic or storage regularly exceeds free limits
- governance requires stronger plugin runtime isolation

Do not upgrade because of ambiguous UI wording in billing screens. Upgrade only for concrete capability requirements.
