# CLI Reference

> Command-line interface for EmDash CMS.

Source: https://emdashcmseverything.com/official-docs/reference/cli/

The EmDash CLI provides commands for database setup, type generation, creating and editing content, schema management, media, and site export and import.

## Installation

The CLI is included with the `emdash` package. Install it with the following command:

```bash
npm install emdash
```

Run commands with `npx emdash` or add scripts to `package.json`. The binary is also available as `em` for brevity.

Start your site with its package script, such as `pnpm dev`. The package script starts Astro; the EmDash integration generates `emdash-env.d.ts`, while the runtime runs pending migrations on the first request and, before setup is completed, applies the bundled seed's schema and structure once. Sample content from the seed is applied only when an administrator chooses it in the setup wizard.

## Authentication

Commands that connect to a running EmDash instance resolve authentication in this order:

1. **`--token` flag** — explicit token on the command line
2. **`EMDASH_TOKEN` env var**
3. **Stored credentials** from `~/.config/emdash/auth.json` (saved by `emdash login`)
4. **Dev bypass** — if the URL is localhost and no token is available, automatically authenticates via the dev bypass endpoint

The `types`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, and `site` commands connect to a running instance. Authentication commands have their own connection options. When targeting a local development server, no token is needed.

## Common flags

Connection flags vary by command. The grouped commands below mean every subcommand in that group.

| Flag                      | Alias | Available on | Description and default |
| ------------------------- | ----- | ------------ | ----------------------- |
| `--url`                   | `-u`  | `types`, `login`, `logout`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, `site` | Instance URL; defaults to `EMDASH_URL` or `http://localhost:4321` |
| `--token`                 | `-t`  | `types`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, `site` | Token from the flag, `EMDASH_TOKEN`, or stored credentials |
| `--header "Name: Value"` | `-H`  | `types`, `login`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, `site` | Repeatable header merged with `EMDASH_HEADERS` and stored headers |
| `--json`                  |       | `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, `site` | Write raw JSON instead of terminal-formatted output |

## Output

When a command writes results to an interactive terminal, it formats them for reading. The commands listed with `--json` above write raw JSON when the flag is set or their output is piped. `emdash migrate` emits JSON only with its explicit `--json` option.

## Commands

### `emdash init`

Initialize a local SQLite database from the template metadata in `package.json`. The command runs core migrations, then applies the optional SQL file named by `emdash.schema`. Run `emdash seed` separately for JSON seed data.

```bash
npx emdash init [options]
```

| Option       | Alias | Description                         | Default       |
| ------------ | ----- | ----------------------------------- | ------------- |
| `--database` | `-d`  | SQLite database path                | `./data.db`   |
| `--cwd`      |       | Project working directory           | Current directory |
| `--force`    | `-f`  | Reapply the template schema when collections already exist | `false` |

Without `--force`, an initialized database is left unchanged. This command opens a local SQLite file directly; use `emdash migrate` for deployment-managed D1, PostgreSQL, libSQL, or Hyperdrive migrations.

### `emdash doctor`

Check a local SQLite database for connection, migration, collection, table, and user problems. If the project has a Wrangler configuration, the command also checks that a Cron Trigger and EmDash `scheduled()` handler are configured together.

```bash
npx emdash doctor [options]
```

| Option       | Alias | Description               | Default       |
| ------------ | ----- | ------------------------- | ------------- |
| `--database` | `-d`  | SQLite database path      | `./data.db`   |
| `--cwd`      |       | Project working directory | Current directory |
| `--json`     |       | Emit structured results   | `false`       |

The command reports each check as pass, warning, or failure and exits non-zero when a check fails.

### `emdash seed`

Validate or apply a JSON seed to a local SQLite database. The command uses the positional path when provided, then `.emdash/seed.json`, then the `emdash.seed` path from `package.json`.

```bash
npx emdash seed [path] [options]
```

| Option             | Alias | Description                                            | Default                          |
| ------------------ | ----- | ------------------------------------------------------ | -------------------------------- |
| `--database`       | `-d`  | SQLite database path                                   | `./data.db`                      |
| `--cwd`            |       | Project working directory                              | Current directory                |
| `--validate`       |       | Validate the seed without changing the database        | `false`                          |
| `--no-content`     |       | Skip entries, bylines, and taxonomy terms               | `false`                          |
| `--on-conflict`    |       | Handle existing records with `skip`, `update`, or `error` | `skip`                         |
| `--uploads-dir`    |       | Local directory used for seed media                    | `./uploads`                      |
| `--media-base-url` |       | Base URL stored for local seed media                   | `/_emdash/api/media/file`        |
| `--skip-media`     |       | Resolve `$media` references to external URLs instead of downloading and storing them | `false` |

Applying a seed runs core migrations first. Use `--validate` in continuous integration when you need to check the file without opening or creating the database.

### `emdash migrate`

Check or apply the core migration set emitted by an Astro build.

```bash
npx emdash migrate [options]
npx emdash migrate --check [options]
npx emdash migrate --status --json [options]
npx emdash migrate --release-lock <id> [options]
npx emdash migrate --take-over-lock <id> [options]
```

By default the command discovers the project root and reads `.emdash/migrations.json`. It validates the manifest against the project's installed EmDash package, resolves the adapter's project-local executor, and prints the immutable target before any SQL.

#### Options

| Option | Description |
| --- | --- |
| `--check` | Apply nothing; exit non-zero for pending or unknown migration records |
| `--status` | Report exact status without applying; exit zero after a successful report |
| `--json` | Emit the stable migration report as JSON |
| `--manifest <path>` | Read a non-standard manifest path |
| `--from-config` | Explicitly evaluate trusted Astro configuration instead of a manifest |
| `--config <path>` | Astro config path used with `--from-config` |
| `--expected-target-fingerprint <sha256>` | Required guard for non-interactive apply, lock release, or lock take-over |
| `--release-lock <id>` | Release the D1 migration lock with the id that `--status` reports; cannot be combined with `--check` or `--status` |
| `--take-over-lock <id>` | Apply pending migrations under the D1 migration lock with the id that `--status` reports, without freeing it in between; cannot be combined with `--check`, `--status`, or `--release-lock` |
| `--database <path>` | Override a SQLite path |
| `--database-url-env <name>` | Override a PostgreSQL connection-variable name |
| `--d1 <uuid-or-name>` | Select a D1 database explicitly |
| `--account-id <id>` | Select a Cloudflare account explicitly |
| `--wrangler-config <path>` | Read D1 binding metadata from an explicit Wrangler config |
| `--wrangler-env <name>` | Select an environment; requires `--wrangler-config` |

Interactive human-readable apply, lock release, and lock take-over ask for confirmation. Non-interactive apply, lock release, or lock take-over, and every one of them using `--json`, require the exact fingerprint printed for the target. There is no `down` or `--dry-run`; use `--check` to determine whether work is required.

#### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Success, including a successful `--status` report |
| `1` | Validation, configuration, target, migration, or cleanup error |
| `2` | `--check` found pending known migrations |
| `3` | `--check` found unknown applied records (takes precedence over pending) |
| `4` | Confirmation missing, declined, or target fingerprint mismatch |
| `130` | Interrupted after bounded executor cleanup |

See [Manage Core Database Migrations](https://emdashcmseverything.com/official-docs/deployment/core-migrations/) for deployment order, target credentials, and the D1 migration lock.

### `emdash types`

Generate TypeScript types from a running EmDash instance's schema.

```bash
npx emdash types [options]
```

#### Options

| Option     | Alias | Description                        | Default                    |
| ---------- | ----- | ---------------------------------- | -------------------------- |
| `--url`    | `-u`  | EmDash instance URL                | `EMDASH_URL` or `http://localhost:4321` |
| `--token`  | `-t`  | Auth token                         | From env or stored credentials |
| `--header` | `-H`  | Custom request header; repeatable  | From env or stored credentials |
| `--json`   |       | Accepted but does not change this command's files or progress output | — |
| `--output` | `-o`  | Output path for types              | `.emdash/types.ts`          |
| `--cwd`    |       | Working directory                  | Current directory          |

#### Examples

```bash
# Generate types from local dev server
npx emdash types

# Generate from remote instance
npx emdash types --url https://example.com

# Custom output path
npx emdash types --output src/types/emdash.ts
```

#### Behavior

1. Fetches the schema from the instance
2. Generates TypeScript type definitions
3. Writes types to the output file
4. Writes `schema.json` alongside for reference

### `emdash login`

Log in to an EmDash instance using OAuth Device Flow.

```bash
npx emdash login [options]
```

#### Options

| Option     | Alias | Description                       | Default                    |
| ---------- | ----- | --------------------------------- | -------------------------- |
| `--url`    | `-u`  | EmDash instance URL               | `EMDASH_URL` or `http://localhost:4321` |
| `--header` | `-H`  | Custom request header; repeatable | From `EMDASH_HEADERS`       |

#### Behavior

1. Discovers auth endpoints from the instance
2. If localhost and no auth configured, uses dev bypass automatically
3. Otherwise initiates OAuth Device Flow — displays a code and opens your browser. After you enter the code, the admin page lists the permissions the CLI will receive, and any requested permissions your role does not allow, before you approve.
4. Polls for authorization, then saves credentials to `~/.config/emdash/auth.json`

Saved credentials are used automatically by all subsequent commands targeting the same instance.

### `emdash logout`

Log out and remove stored credentials.

```bash
npx emdash logout [options]
```

#### Options

| Option  | Alias | Description           | Default                |
| ------- | ----- | --------------------- | ---------------------- |
| `--url` | `-u`  | EmDash instance URL | `EMDASH_URL` or `http://localhost:4321` |

### `emdash whoami`

Show the current authenticated user.

```bash
npx emdash whoami [options]
```

#### Options

| Option                   | Alias | Description           | Default                                  |
| ------------------------ | ----- | --------------------- | ---------------------------------------- |
| `--url`                  | `-u`  | EmDash instance URL   | `EMDASH_URL` or `http://localhost:4321` |
| `--token`                | `-t`  | Auth token            | From env/stored creds                    |
| `--header "Name: Value"` | `-H`  | Custom header, repeatable | Merged with `EMDASH_HEADERS`         |
| `--json`                 |       | Output as JSON        |                                          |

Displays email, name, role, auth method, and instance URL. On localhost with no token or stored credentials, `whoami` signs in through the dev bypass, which creates the `dev@emdash.local` admin user if it doesn't exist, and reports that user. It exits with a non-zero status when the instance can't be reached or the dev bypass is unavailable, for example when the site isn't running under `astro dev`.

### `emdash content`

Manage content items. All subcommands use the remote API via `EmDashClient`.

#### `content list <collection>`

```bash
npx emdash content list posts
npx emdash content list posts --status published --limit 10
```

| Option     | Description          |
| ---------- | -------------------- |
| `--status` | Filter by status     |
| `--locale` | Filter by locale     |
| `--limit`  | Maximum items        |
| `--cursor` | Pagination cursor    |

#### `content get <collection> <id>`

```bash
npx emdash content get posts 01ABC123
npx emdash content get posts 01ABC123 --raw
```

| Option        | Description                                           |
| ------------- | ----------------------------------------------------- |
| `--locale`    | Locale to use when the ID argument is a slug          |
| `--raw`       | Return raw Portable Text instead of Markdown          |
| `--published` | Ignore a pending draft and return published data only |

The response includes a `_rev` token. Pass it to `content update` to confirm you have seen the current state before overwriting it.

#### `content create <collection>`

```bash
npx emdash content create posts --data '{"title": "Hello"}'
npx emdash content create posts --file post.json --slug hello-world
cat post.json | npx emdash content create posts --stdin
```

| Option     | Description                    |
| ---------- | ------------------------------ |
| `--data`            | JSON string with content data                          |
| `--file`            | Read data from a JSON file                             |
| `--stdin`           | Read data from stdin                                   |
| `--slug`            | Content slug                                           |
| `--locale`          | Content locale                                         |
| `--translation-of`  | ID of a content item to link this as a translation of  |
| `--draft`           | Keep as draft instead of auto-publishing               |

Provide data via exactly one of `--data`, `--file`, or `--stdin`. New items are auto-published unless `--draft` is set.

#### `content update <collection> <id>`

You must provide the `_rev` token from a prior `get` to prove you have seen the current state. This prevents overwriting changes you have not seen. The following steps read an item, then update it with that token:

```bash
# 1. Read the item, note the _rev
npx emdash content get posts 01ABC123

# 2. Update with the _rev from step 1
npx emdash content update posts 01ABC123 \
  --rev MToyMDI2LTAyLTE0... \
  --data '{"title": "Updated"}'
```

| Option            | Description                                              |
| ----------------- | -------------------------------------------------------- |
| `--rev`           | Revision token from `get` (required)                     |
| `--data`          | JSON string with content data                            |
| `--file`          | Read data from a JSON file                               |
| `--locale`        | Locale to use when the ID argument is a slug              |
| `--draft`         | Keep the update as a draft instead of auto-publishing     |
| `--override-lock` | Write even though another editor has the entry open      |

If the item has changed since your `get`, the server returns 409 Conflict — re-read and try again.

If someone has the entry open in the admin, the server returns 409 with code
`ENTRY_LOCKED` and a message that names the holder. Wait for them to finish, or
pass `--override-lock`. The same flag is available on `content delete`,
`content publish`, `content unpublish` and `content schedule`.

#### `content delete <collection> <id>`

```bash
npx emdash content delete posts 01ABC123
```

Soft-deletes the content item (moves to trash).

Pass `--override-lock` to delete an entry that another editor has open.

#### `content publish <collection> <id>`

```bash
npx emdash content publish posts 01ABC123
```

Pass `--override-lock` to publish an entry that another editor has open.

#### `content unpublish <collection> <id>`

```bash
npx emdash content unpublish posts 01ABC123
```

Pass `--override-lock` to unpublish an entry that another editor has open.

#### `content schedule <collection> <id>`

```bash
npx emdash content schedule posts 01ABC123 --at 2026-03-01T09:00:00Z
```

| Option | Description                     |
| ------ | ------------------------------- |
| `--at` | ISO 8601 datetime with `Z` or an explicit UTC offset (required) |

Pass `--override-lock` to schedule an entry that another editor has open.

#### `content restore <collection> <id>`

```bash
npx emdash content restore posts 01ABC123
```

Restores a trashed content item.

#### `content translations <collection> <id>`

List every translation in the entry's translation group:

```bash
npx emdash content translations posts 01ABC123
```

The result includes each translation's ID, locale, slug, status, and whether it is the requested entry.

### `emdash schema`

Manage collections and fields.

#### `schema list`

```bash
npx emdash schema list
```

Lists all collections.

#### `schema get <collection>`

```bash
npx emdash schema get posts
```

Shows a collection with all its fields.

#### `schema create <collection>`

```bash
npx emdash schema create articles --label Articles
npx emdash schema create articles --label Articles --label-singular Article --description "Blog articles"
```

| Option             | Description                     |
| ------------------ | ------------------------------- |
| `--label`          | Collection label (required)     |
| `--label-singular` | Singular label                  |
| `--description`    | Collection description          |

#### `schema delete <collection>`

```bash
npx emdash schema delete articles
npx emdash schema delete articles --force
```

| Option    | Description         |
| --------- | ------------------- |
| `--force` | Skip confirmation   |

Prompts for confirmation unless `--force` is set.

#### `schema add-field <collection> <field>`

```bash
npx emdash schema add-field posts body --type portableText --label "Body Content"
npx emdash schema add-field posts featured --type boolean --required
```

| Option       | Description                                                                                      |
| ------------ | ------------------------------------------------------------------------------------------------ |
| `--type`     | Field type: string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file, reference, json, slug, or repeater (required) |
| `--label`    | Field label (defaults to field slug)                                                             |
| `--required` | Whether the field is required                                                                    |

#### `schema remove-field <collection> <field>`

```bash
npx emdash schema remove-field posts featured
```

### `emdash media`

Manage media items.

#### `media list`

```bash
npx emdash media list
npx emdash media list --mime image/png --limit 20
```

| Option     | Description              |
| ---------- | ------------------------ |
| `--mime`   | Filter by MIME type      |
| `--limit`  | Number of items          |
| `--cursor` | Pagination cursor        |

#### `media upload <file>`

```bash
npx emdash media upload ./photo.jpg
npx emdash media upload ./photo.jpg --alt "A sunset" --caption "Taken in Bristol"
```

| Option      | Description    |
| ----------- | -------------- |
| `--alt`     | Alt text       |
| `--caption` | Caption text   |

#### `media get <id>`

```bash
npx emdash media get 01MEDIA123
```

#### `media delete <id>`

```bash
npx emdash media delete 01MEDIA123
```

#### `media repair-usage`

Repair content media usage indexes for one collection or for every content collection. Use this after imports or direct database writes when usage coverage is stale or untrusted.

```bash
npx emdash media repair-usage --collection posts
npx emdash media repair-usage --all
npx emdash media repair-usage --all --json
```

| Option         | Alias | Description                    |
| -------------- | ----- | ------------------------------ |
| `--collection` | `-c`  | Repair one content collection  |
| `--all`        |       | Repair every content collection |

Pass exactly one of `--collection` or `--all`. Remote repair requires an Admin user and an auth token with the `admin` scope.

All-content repair runs synchronously and can be slow or expensive on large sites. Prefer `--collection` when you only need to repair one collection.

Structured `complete`, `partial`, and `stale` repair results exit `0`; structured `failed` results exit `1`. Automation and cron jobs should use `--json` and parse `status`, `failedSourceCount`, `skippedSourceCount`, and per-collection summaries instead of treating exit `0` as complete coverage.

### `emdash search`

Full-text search across content.

```bash
npx emdash search "hello world"
npx emdash search "hello" --collection posts --limit 5
```

| Option         | Alias | Description          |
| -------------- | ----- | -------------------- |
| `--collection` | `-c`  | Filter by collection |
| `--locale`     |       | Filter by locale     |
| `--limit`      | `-l`  | Maximum results      |

### `emdash taxonomy`

Manage taxonomies and terms.

#### `taxonomy list`

```bash
npx emdash taxonomy list
```

#### `taxonomy terms <name>`

```bash
npx emdash taxonomy terms categories
npx emdash taxonomy terms tags --limit 50
```

| Option     | Alias | Description       |
| ---------- | ----- | ----------------- |
| `--limit`  | `-l`  | Maximum terms     |
| `--cursor` |       | Pagination cursor |

#### `taxonomy add-term <taxonomy>`

```bash
npx emdash taxonomy add-term categories --name "Tech" --slug tech
npx emdash taxonomy add-term categories --name "Frontend" --parent 01PARENT123
```

| Option     | Description                              |
| ---------- | ---------------------------------------- |
| `--name`   | Term label (required)                    |
| `--slug`   | Term slug (defaults to slugified name)   |
| `--parent` | Parent term ID (for hierarchical taxonomies) |

### `emdash menu`

Manage navigation menus.

#### `menu list`

```bash
npx emdash menu list
```

#### `menu get <name>`

```bash
npx emdash menu get primary
```

Returns the menu with all its items.

### `emdash site`

Export a whole site to a `.emdash` site package, and import a package into an empty site. The [site transfer guide](https://emdashcmseverything.com/official-docs/guides/site-transfer/) explains what a package contains, what the target site needs, and how to read an import plan.

The token needs the `admin` scope, which the `emdash login` token has, or the matching transfer scopes: `transfer:export` to export, and `transfer:analyze` and `transfer:execute` to import. A token without them fails with `INSUFFICIENT_SCOPE`.

Progress messages always go to stderr, and the result goes to stdout. With `--json`, or when stdout is not a terminal, stdout contains only the JSON result. An error is written as `{ "error": { "code": "…", "message": "…" } }`. The codes are the server's error codes, plus `INVALID_ARGUMENT` for bad flags, `PACKAGE_FILE_REQUIRED` when a resumed import still needs the package file, and `UNKNOWN_ERROR`.

The commands retry network failures and `408`, `429`, and `5xx` responses with backoff.

#### `site export`

Export the site and write it to a package file:

```bash
npx emdash site export --output site.emdash
```

| Option          | Alias | Description                                        | Default           |
| --------------- | ----- | -------------------------------------------------- | ----------------- |
| `--output`      | `-o`  | Package file to write (required)                   |                   |
| `--no-comments` |       | Leave comments and comment reactions out           | Comments included |

The command starts an export, advances it until it completes, and downloads the package file by file. It checks that the downloaded manifest matches the export's package digest, and fails with `TRANSFER_PACKAGE_DIGEST_MISMATCH` before writing anything if it does not. It checks each file's size and SHA-256 digest before writing it. The package is written to `<output>.partial` and renamed to the output path when it is complete.

The command keeps its progress in `<output>.partial.json` and the downloaded files in the `<output>.parts/` directory. Run the same command again after an interruption to resume the same export; files already downloaded are checked and reused, and the command reports how many it reused. Both are deleted when the package is written. The progress file is ignored when it was written for another URL or another comments setting, or when its export failed or expired; the command then starts a new export.

The JSON result contains `operationId`, `output`, `packageDigest`, `files`, `bytes`, and `resumed`.

#### `site import <file>`

Import a package in two steps. Analyze it first, then confirm the plan digest that analysis printed:

```bash
npx emdash site import site.emdash --analyze
npx emdash site import site.emdash --plan sha256:3f1c… --confirm
```

| Option                   | Description                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `--analyze`              | Upload the package, analyze it, and print the import plan                                            |
| `--map-principal <from>=<to>` | With `--analyze`: map a package principal, by ID or email address, to a site user by ID or email address, or to `none`. Repeatable |
| `--use-target-title`     | With `--analyze`: keep this site's title instead of the package's                                     |
| `--use-target-tagline`   | With `--analyze`: keep this site's tagline instead of the package's                                   |
| `--plan <digest>`        | The plan digest to execute, as `sha256:<hex>` or bare hex. Requires `--confirm`                      |
| `--confirm`              | Execute the plan given by `--plan`. Requires `--plan`                                                |
| `--yes`                  | Alias `-y`. With `cancel` or `abandon`: skip the confirmation prompt                                 |

`--analyze` verifies the whole package file locally, then finds the site's existing import of the same package or creates one. It uploads the files the site does not have yet, runs analysis, and prints the plan: the package and plan digests, record counts, sizes, the title and tagline choice, each principal and its mapping, the transformations under "Differences from the source site", the warnings, and the blockers. If an earlier import of the same package failed, was cancelled or abandoned, or expired, the command warns and starts a new import.

Decisions are stored with the import, so a later `--analyze` run without decision flags keeps them. Each change of decisions produces a new plan digest. Decisions cannot be combined with `--plan`, and `--plan` cannot be combined with `--analyze`.

`--plan <digest> --confirm` executes the import only when the digest matches the current plan, then advances it until it completes and prints the receipt. If the plan changed since you reviewed it, the command fails with `TRANSFER_PLAN_DIGEST_MISMATCH`; analyze again and confirm the new digest.

The JSON result of `--analyze` contains `operationId`, `state`, `packageDigest`, `planDigest`, `executable`, and the full `plan`. The JSON result of `--confirm` contains `operationId`, `state` (`complete`), `receipt`, and `receiptDigestValid`, which reports whether the receipt's `receiptDigest` matches its contents.

These forms operate on an import by its operation ID:

| Command | Description |
| --- | --- |
| `emdash site import status <operation-id>` | Print the import's state, stage, progress, digests, and error, and how many files are uploaded. The JSON result is `{ operation, files: { declared, verified } }`. |
| `emdash site import resume <operation-id> [file]` | Continue the import from its current state: finish uploading, finish analysis and print the plan, or finish execution and print the receipt. Pass the package file only while the import is still uploading. |
| `emdash site import receipt <operation-id>` | Print the receipt of a complete import, in the same shape as `--confirm`. |
| `emdash site import cancel <operation-id>` | Cancel the import. A running import stops after its current batch; what it already wrote stays on the site. |
| `emdash site import abandon <operation-id>` | Lift the write block that a failed or cancelled import leaves on the site. What the import wrote is not deleted, so reset the site or set up a new one before importing again. |

`cancel` and `abandon` ask for confirmation. Pass `--yes` to skip the prompt; the prompt is also skipped with `--json` or when stdout is not a terminal. When stdin is not a terminal and neither applies, the command fails with `INVALID_ARGUMENT`. Declining the prompt changes nothing and exits with code `1`. The JSON result of both is `{ operationId, state, operation }`.

The import commands exit with these codes:

| Code | Meaning |
| --- | --- |
| `0` | Success. For `status`, an import that is in progress or complete |
| `1` | An error, a declined prompt, an import that ended without a receipt, or a receipt whose digest does not match. For `status`, an import that failed, was cancelled or abandoned, or expired |
| `2` | Analysis finished, but the plan has blockers |

### `emdash export-seed`

Export database schema and content as a seed file. Works directly on a local SQLite file.

The database must have every migration known to the installed EmDash version. If the command
reports pending migrations, run `npx emdash migrate`, then export again. If the database was
migrated by a newer EmDash version, upgrade the installed version before exporting. The export
opens the database read-only and never applies migrations itself.

```bash
npx emdash export-seed [options] > seed.json
```

#### Options

| Option           | Alias | Description                                          | Default           |
| ---------------- | ----- | ---------------------------------------------------- | ----------------- |
| `--database`     | `-d`  | Database file path                                   | `./data.db`       |
| `--cwd`          |       | Working directory                                    | Current directory |
| `--with-content` |       | Include content (all or comma-separated collections) |                   |
| `--pretty` / `--no-pretty` |       | Enable or disable indented JSON output              | Pretty output enabled |
| `--media-base-url` |     | Public URL of the site, used to write absolute `$media` URLs |           |

#### Output format

The exported seed file includes:

- **Settings**: Site title, tagline, social links
- **Collections**: All collection definitions with fields
- **Block types**: Every retained version and each type's active version pointer
- **Taxonomies**: Taxonomy definitions and terms
- **Menus**: Navigation menus with items
- **Redirects**: Redirect rules with status 301, 302, 307, or 308
- **Widget Areas**: Widget areas and widgets
- **Sections**: Reusable content blocks
- **Content** (if requested): Entries with `$media` references and `$ref:` syntax for portability

Scheduled entries are exported as drafts, because a seed has no field for a publish time. The export leaves out, with a warning on stderr, anything `emdash seed` would reject: redirect rules with status 410 or 451, extra rules that share a source (possible in older databases), and sections whose slug contains characters other than lowercase letters, digits, and hyphens.

#### Media URLs

`emdash seed` downloads each `$media` URL and uploads the file to the target site's storage, so it needs an absolute `http` or `https` URL it can reach. Pass the source site's public URL to write absolute URLs:

```bash
npx emdash export-seed --with-content=all --media-base-url=https://example.com > seed.json
```

The site must serve its media from `/_emdash/api/media/file/` under that URL while the seed is applied, and the URL must not point at `localhost` or a private network address, which `emdash seed` refuses to download from. Without `--media-base-url`, `$media` URLs are site-relative paths that `emdash seed` skips, leaving the fields empty, and the export prints a warning on stderr.

Image and file fields, and image sub-fields of repeaters, are exported as `$media` references. Images inside Portable Text fields keep their stored media ID and URL, which do not resolve on a different site.

### `emdash secrets`

Generate and inspect the key used to encrypt plugin secrets.

#### `secrets generate`

Generate an `EMDASH_ENCRYPTION_KEY` for your deployment. The key is used to
encrypt plugin secrets at rest.

```bash
npx emdash secrets generate
```

Prints the new key to stdout. Pipe it into your secret store, or write it
straight to your local `.env` file with `--write`. Wrangler and the Cloudflare
Vite plugin read that file in local development. A standalone Node server does
not load `.env` automatically; load it through the process manager or provide
the key through the server's process environment. The [Node.js deployment
guide](https://emdashcmseverything.com/official-docs/deployment/nodejs/#build-and-run) shows the local command.

```bash
npx emdash secrets generate --write .env
```

`--write` refuses to overwrite an existing entry without `--force`. To rotate a deployment with existing encrypted data, prepend the generated key to the existing value and separate the keys with a comma. EmDash encrypts new values with the first key and uses older entries for decryption by `kid`. Re-save every plugin secret before removing an old key. EmDash does not currently list the key IDs still used by stored settings, so keep an inventory of the credentials you resave and verify each integration before removing its old key.

#### `secrets fingerprint <key>`

Print the 8-character fingerprint (kid) of a key without exposing its
value. This is useful in CI for verifying the right key was deployed. The following command prints a key's fingerprint:

```bash
npx emdash secrets fingerprint emdash_enc_v1_...
```

## Generated files

### `emdash-env.d.ts`

The Astro integration generates `emdash-env.d.ts` in the project root when the local development server starts. It refreshes the file after schema changes made through the running development site. The declarations augment `EmDashCollections`, so calls such as `getEmDashCollection("posts")` infer the fields defined in the local database.

This file is automatic and belongs to the local Astro development workflow. You do not need to run `emdash types` to create it.

### `.emdash/types.ts`

The `emdash types` command fetches a running instance's schema and writes standalone TypeScript interfaces. Use it when the schema lives on a remote EmDash instance, when tooling needs a file at a custom path, or when the local Astro development server is not running:

```ts title=".emdash/types.ts"
// Generated by EmDash CLI
// Do not edit manually - run `emdash types` to regenerate

import type { PortableTextBlock } from "emdash";

export interface Post {
	id: string;
	slug: string | null;
	status: string;
	title: string;
	content?: PortableTextBlock[];
	createdAt: Date;
	updatedAt: Date;
	publishedAt: Date | null;
	bylines?: ContentBylineCredit[];
	terms?: Record<string, TaxonomyTerm[]>;
}
```

The remote output contains standalone collection interfaces and does not augment `EmDashCollections`. It changes only when you run `emdash types`; `emdash-env.d.ts` uses module augmentation and refreshes as part of local development.

> **Caution**
>
> The current remote output always includes `bylines` and `terms`, but does not import their
> `ContentBylineCredit` and `TaxonomyTerm` types. Add both names to the generated `import type`
> line before type-checking the file. Running `emdash types` again overwrites that manual fix.

### `.emdash/schema.json`

The command also writes a raw schema export named `schema.json` beside the selected TypeScript output. With the default output path, the file is `.emdash/schema.json`:

```json title=".emdash/schema.json"
{
  "version": "a1b2c3d4",
  "collections": [
    {
      "slug": "posts",
      "label": "Posts",
      "fields": [...]
    }
  ]
}
```

## Environment variables

| Variable                  | Description                              |
| ------------------------- | ---------------------------------------- |
| `EMDASH_DATABASE_URL`     | Override database URL                      |
| `EMDASH_TOKEN`            | Auth token for remote operations          |
| `EMDASH_URL`              | Default URL for commands using the shared remote client, `login`, `logout`, and `whoami` |
| `EMDASH_HEADERS`          | Newline-separated custom request headers for the shared remote client and `login` |
| `EMDASH_ENCRYPTION_KEY`   | Key for encrypting plugin secrets at rest. Operator-provided — never stored in the database. Generate with `emdash secrets generate`. |
| `EMDASH_PREVIEW_SECRET`   | Optional override for preview HMAC secret. When unset, EmDash generates and persists one in the options table. |
| `EMDASH_IP_SALT`          | Optional override for the commenter-IP hash salt. When unset, EmDash generates and persists one in the options table. |
| `EMDASH_AUTH_SECRET`      | Legacy. Used as the IP-salt source if set, so existing installs keep stable commenter-IP hashes across upgrade. New installs should not set this. |

## Package scripts

Add common commands as `package.json` scripts for convenience:

```json title="package.json"
{
	"scripts": {
		"dev": "astro dev",
		"types": "emdash types",
		"export-seed": "emdash export-seed",
		"db:reset": "rm -f data.db"
	}
}
```

## General exit codes

Most commands use `0` for success and `1` for an error. `emdash migrate` also uses codes `2`, `3`, `4`, and `130` for the specific outcomes listed in its [exit-code table](#exit-codes). `emdash site import` uses `2` when the import plan has blockers.

| Code | Description                              |
| ---- | ---------------------------------------- |
| `0`  | Success                                  |
| `1`  | Error (configuration, network, database) |
