How to Set Up a Staging Environment in EmDash CMS

A staging environment matters most right before a destructive schema change — removing a field, restructuring a collection — where testing against a disposable copy of production is genuinely safer than finding out what breaks live. EmDash's own deployment docs describe exactly this pattern for Cloudflare Workers; the same idea generalizes cleanly to Node.js hosting.
Table of Contents
- The Cloudflare Workers Pattern (Documented)
- Rehearsing a Schema Change on Staging
- The Node.js Equivalent: Environment-Based Configuration
- Keeping Staging's Content Model in Sync
- Recovering From a Mistake on Staging (Where It's Meant to Happen)
- Frequently Asked Questions
- Does staging need to be Cloudflare-specific?
- How often should I refresh staging's data from production?
- Can I just test schema changes locally instead of on a real staging deployment?
- Does EmDash charge extra for a staging deployment?
- The Bottom Line
The Cloudflare Workers Pattern (Documented)
Add a `preview` environment block to `wrangler.jsonc`, with its own D1 database:
{
"env": {
"preview": {
"d1_databases": [
{ "binding": "DB", "database_name": "emdash-db-preview" }
]
}
}
}Copy production data into it — a straight export and re-import:
npx wrangler d1 export emdash-db --remote --output=./prod.sql
npx wrangler d1 execute emdash-db-preview --remote --file=./prod.sqlDeploy to the preview environment specifically:
npx wrangler deploy --env previewThat gives you a fully separate, live URL running against a real (point-in-time) copy of your production content — safe to test a risky schema change against before touching the real database.
Rehearsing a Schema Change on Staging
EmDash's `emdash schema` CLI commands talk to a running instance over its REST API, so the same commands that would modify production work identically against your preview URL first:
npx emdash schema remove-field posts legacy_field --url https://preview.your-site.workers.dev- Run the schema change against the preview URL.
- Verify the site renders correctly and the admin panel behaves as expected.
- Only then, run the identical command against your production URL.
Removing a field deletes its column and data. Test schema changes against a preview environment before running them against production.
The Node.js Equivalent: Environment-Based Configuration
For non-Cloudflare deployments, the same idea works with a config that picks a different database depending on environment — no wrangler-specific tooling required, just a conditional in `astro.config.mjs`:
import { sqlite, postgres } from "emdash/db";
const database = process.env.STAGING
? postgres({ connectionString: process.env.STAGING_DATABASE_URL })
: postgres({ connectionString: process.env.DATABASE_URL });
export default defineConfig({
integrations: [emdash({ database })],
});Deploy this same codebase twice — once with `DATABASE_URL` pointed at your real production database, once with `STAGING=true` and `STAGING_DATABASE_URL` pointed at a separate database — and you have two independent EmDash instances sharing one codebase, exactly the same principle as the Cloudflare preview pattern.
Keeping Staging's Content Model in Sync
A staging environment is only useful if it reflects your real content model, not a stale one. EmDash's seed file is the mechanism for this — export the live schema (and optionally content) and commit it, so any fresh environment (staging included) bootstraps to the current model rather than an outdated one:
npx wrangler d1 export emdash-db --remote --output=./prod.sql
sqlite3 prod.db < prod.sql
npx emdash export-seed --database prod.db > .emdash/seed.jsonCommit the updated `.emdash/seed.json` alongside the code that depends on the new schema. That way, a fresh staging environment bootstrapped from an empty database ends up with the same content model production actually runs, not the starter template's default.
Recovering From a Mistake on Staging (Where It's Meant to Happen)
- A field was removed by mistake — restore from a D1 Time Travel point-in-time backup, or re-add the field and restore its values from an earlier `wrangler d1 export`.
- A fresh environment bootstrapped with the wrong model — the embedded seed was stale; update `.emdash/seed.json`, rebuild, and re-bootstrap against an empty database.
- Schema and templates disagree — order changes deliberately: additive schema changes (new collection, new optional field) deploy before the code using them; for removals, deploy the code that stops using a field first, then remove the field.
Frequently Asked Questions
Does staging need to be Cloudflare-specific?
No — the Cloudflare pattern is the one EmDash documents explicitly (using D1's export/import and Wrangler's environment blocks), but the underlying idea — a second deployment, a separate database — works identically on any Node.js hosting target using environment variables.
How often should I refresh staging's data from production?
Before any significant testing session, and definitely before rehearsing a destructive schema change — stale staging data can hide a real conflict that only shows up against current production content.
Can I just test schema changes locally instead of on a real staging deployment?
For non-destructive changes, local SQLite testing is often enough. For genuinely destructive changes (field removal, collection restructuring) on a production D1/Postgres database, testing against a real copy of production data specifically — not a local dev database with sample data — catches issues local testing can miss.
Does EmDash charge extra for a staging deployment?
No — EmDash itself has no licensing cost at any scale. A staging environment's cost is purely the infrastructure it runs on (a second D1 database and Worker, or a second Node.js deployment), which is typically minimal for a low-traffic preview environment.
The Bottom Line
A staging environment for EmDash is fundamentally a second deployment pointed at a separate database copy — documented explicitly for Cloudflare's D1/Wrangler workflow, and straightforward to replicate on any Node.js host with environment-based configuration. Use it specifically before destructive schema changes, and keep its seed file current so it reflects your real content model. See our guide to customizing content models for the schema changes worth rehearsing this way.




Comments