EmDash stores collections, fields, and taxonomies in the database beside the content. Use this guide to change that live content model without confusing it with a code deploy, a first-time seed, or an EmDash core migration. The examples use Cloudflare D1; the same separation applies to every database adapter.
What changes what
A site goes through four distinct workflows. Each one touches a different layer:
| Workflow | What changes | How |
|---|---|---|
| Content editing | Entries, media, settings | Admin panel or content API |
| Code deploy | Templates, config, EmDash version | wrangler deploy — may migrate EmDash-managed database tables |
| First-time bootstrap | Everything, from empty | Migrations + seed file + setup wizard, automatic on first boot |
| Schema evolution | Collections, fields, taxonomies | Admin panel or emdash schema against the live site (this page) |
The seed file only participates in the third row. It is applied once, when the database is empty and the setup wizard has not been completed. Deploying a changed seed file against an existing database does nothing — evolving a live site’s schema always happens through the admin panel or the API.
Change the schema in the admin panel
The admin panel is the primary way to evolve a deployed site. Open Content Types in the admin and add, edit, or remove collections and fields. Changes take effect immediately — the content API, the loader, and the editing UI all read the schema from the database at runtime.
See Collections & Fields for the available field types, validation rules, and widget options.
After changing the schema, regenerate the TypeScript types your templates use. The emdash types command reads the schema from a running instance, so it can point at the deployed site directly:
npx emdash types --url https://example.com
Change the schema from the CLI
The emdash schema commands talk to a running instance over its REST API, so they work against a deployed site the same way they work against local dev. Authenticate once with the device flow:
npx emdash login --url https://example.com
Alternatively, create an API token in the admin under Settings → API Tokens and pass it with --token or the EMDASH_TOKEN environment variable — useful for CI.
Then evolve the schema with the same commands you would use locally:
npx emdash schema add-field posts subtitle --type string --label "Subtitle" --url https://example.com
npx emdash schema remove-field posts legacy_field --url https://example.com
npx emdash schema create projects --label Projects --url https://example.com
These commands can be checked into a script so each environment receives the same ordered change. The commands are not automatically idempotent: rerunning create or add-field against an object that already exists can fail. Inspect the target with emdash schema list or get, record which environment completed each step, and stop on the first error.
See the CLI reference for the full command list.
Keep the seed file in sync
The seed file embedded in your build determines what a fresh database initializes to: a new preview environment, a disaster-recovery rebuild, or a second deployment of the same site. If the seed still describes the starter blog while production has evolved into something else, every fresh environment bootstraps with the wrong model.
The build embeds the first seed file found at .emdash/seed.json, the path in package.json#emdash.seed, or seed/seed.json. If none is present, a built-in default seed (the starter blog model) is embedded, and astro dev logs a warning.
After evolving a deployed site’s schema, export the live model back into your repository. emdash export-seed reads a local SQLite file, and wrangler d1 export produces one from the deployed D1 database:
npx wrangler d1 export emdash-db --remote --output=./prod.sql
sqlite3 prod.db < prod.sql
npx emdash export-seed --database prod.db > .emdash/seed.json
The exported seed contains the settings, collections, taxonomies, menus, redirects, widget areas, and sections of the live site. Add --with-content to include entries. Commit the updated .emdash/seed.json together with the code that depends on the new schema, so a fresh environment always bootstraps to a model the code understands.
Rehearse changes on a preview environment
A destructive schema change (removing a field, restructuring a collection) is safest rehearsed against a disposable copy of production.
-
Create a separate preview D1 database and let Wrangler add it to the
previewenvironment:npx wrangler d1 create emdash-db-preview \ --binding DB --env preview --update-configConfirm that
env.preview.d1_databasescontains the new database name and UUID. Bindings are not inherited from the top-level Wrangler configuration. -
Export production, then import the SQL through the preview environment’s
DBbinding:npx wrangler d1 export emdash-db --remote --output=./prod.sql npx wrangler d1 execute DB --env preview --remote --file=./prod.sql -
Build the project, deploy it to the preview environment, then run the schema change against the preview URL:
npm run build npx wrangler deploy --env preview npx emdash schema remove-field posts legacy_field --url https://preview.example.com -
Verify the public pages, admin forms, generated types, and any template that reads the changed fields. Take a fresh production database backup, then run the same commands once against production.
Recover from a wrong turn
- A field was removed by mistake. The column and its data are gone from the live database. 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 or missing. Update
.emdash/seed.json(see Keep the seed file in sync), rebuild, and point the deploy at an empty database to bootstrap again. - The schema and the templates disagree. Deploys and schema changes are independent, so order them deliberately: additive schema changes (new collection, new optional field) go first, then the code that uses them. For removals, deploy the code that stops using the field first, then remove the field.