The EmDash CLI provides commands for database setup, type generation, creating and editing content, schema management, media, and plugin development.
Installation
The CLI is included with the emdash package. Install it with the following command:
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 applies the bundled seed when the database is empty and setup has not been completed.
Authentication
Commands that connect to a running EmDash instance resolve authentication in this order:
--tokenflag — explicit token on the command lineEMDASH_TOKENenv var- Stored credentials from
~/.config/emdash/auth.json(saved byemdash login) - 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, and menu 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 | Instance URL; defaults to EMDASH_URL or http://localhost:4321 |
--token | -t | types, whoami, content, schema, media, search, taxonomy, menu | Token from the flag, EMDASH_TOKEN, or stored credentials |
--header "Name: Value" | -H | types, login, content, schema, media, search, taxonomy, menu | Repeatable header merged with EMDASH_HEADERS and stored headers |
--json | whoami, content, schema, media, search, taxonomy, menu | 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.
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.
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.
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 |
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.
npx emdash migrate [options]
npx emdash migrate --check [options]
npx emdash migrate --status --json [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 |
--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 asks for confirmation. Non-interactive apply and every apply 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 for deployment order, target credentials, and D1 serialization.
emdash dev (deprecated)
The legacy command initializes and migrates a local SQLite database before starting Astro. That behavior does not use the database adapter configured by the site and is incompatible with Cloudflare D1 development. Existing invocations now print a deprecation warning before doing any database work.
| Option | Alias | Description | Default |
|---|---|---|---|
--database | -d | Local SQLite database path | ./data.db |
--types | -t | Fetch remote types before starting Astro | false |
--port | -p | Astro development-server port | 4321 |
--cwd | Project working directory | Current directory |
emdash types
Generate TypeScript types from a running EmDash instance’s schema.
npx emdash types [options]
Options
| Option | Alias | Description | Default |
|---|---|---|---|
--url | -u | EmDash instance URL | 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
# Generate types from local dev server
npx emdash types
# Generate from remote instance
npx emdash types --url https://my-site.pages.dev
# Custom output path
npx emdash types --output src/types/emdash.ts
Behavior
- Fetches the schema from the instance
- Generates TypeScript type definitions
- Writes types to the output file
- Writes
schema.jsonalongside for reference
emdash login
Log in to an EmDash instance using OAuth Device Flow.
npx emdash login [options]
Options
| Option | Alias | Description | Default |
|---|---|---|---|
--url | -u | EmDash instance URL | http://localhost:4321 |
--header | -H | Custom request header; repeatable | From EMDASH_HEADERS |
Behavior
- Discovers auth endpoints from the instance
- If localhost and no auth configured, uses dev bypass automatically
- Otherwise initiates OAuth Device Flow — displays a code and opens your browser
- 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.
npx emdash logout [options]
Options
| Option | Alias | Description | Default |
|---|---|---|---|
--url | -u | EmDash instance URL | http://localhost:4321 |
emdash whoami
Show the current authenticated user.
npx emdash whoami [options]
Options
| Option | Alias | Description | Default |
|---|---|---|---|
--url | -u | EmDash instance URL | http://localhost:4321 |
--token | -t | Auth token | From env/stored creds |
--json | Output as JSON |
Displays email, name, role, auth method, and instance URL.
emdash content
Manage content items. All subcommands use the remote API via EmDashClient.
content list <collection>
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>
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>
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:
# 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>
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>
npx emdash content publish posts 01ABC123
Pass --override-lock to publish an entry that another editor has open.
content unpublish <collection> <id>
npx emdash content unpublish posts 01ABC123
Pass --override-lock to unpublish an entry that another editor has open.
content schedule <collection> <id>
npx emdash content schedule posts 01ABC123 --at 2026-03-01T09:00:00Z
| Option | Description |
|---|---|
--at | ISO 8601 datetime (required) |
Pass --override-lock to schedule an entry that another editor has open.
content restore <collection> <id>
npx emdash content restore posts 01ABC123
Restores a trashed content item.
content translations <collection> <id>
List every translation in the entry’s translation group:
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
npx emdash schema list
Lists all collections.
schema get <collection>
npx emdash schema get posts
Shows a collection with all its fields.
schema create <collection>
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>
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>
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>
npx emdash schema remove-field posts featured
emdash media
Manage media items.
media list
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>
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>
npx emdash media get 01MEDIA123
media delete <id>
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.
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.
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
npx emdash taxonomy list
taxonomy terms <name>
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>
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
npx emdash menu list
menu get <name>
npx emdash menu get primary
Returns the menu with all its items.
emdash plugin
Create, validate, bundle, and publish EmDash plugins. Marketplace login is separate from login to a CMS instance.
plugin init
Scaffold a sandboxed or native plugin:
npx emdash plugin init --dir ./my-plugin --name my-plugin --format sandboxed
| Option | Description | Default |
|---|---|---|
--dir | Directory to create | Current directory |
--name | Plugin package name or ID | Interactive prompt |
--format | sandboxed or native | Interactive prompt |
--native | Shortcut for --format native | false |
plugin bundle
Validate a plugin and create its marketplace tarball:
npx emdash plugin bundle --dir ./my-plugin --outDir ./artifacts
| Option | Alias | Description | Default |
|---|---|---|---|
--dir | Plugin directory | Current directory | |
--outDir | -o | Tarball output directory | ./dist |
--validateOnly | Run validation without creating a tarball | false |
plugin validate
Run the same validation as plugin bundle without creating a tarball:
npx emdash plugin validate --dir ./my-plugin
The optional --dir selects the plugin directory and defaults to the current directory.
plugin publish
Upload a bundle to the marketplace and, by default, wait for its processing result:
npx emdash plugin publish --tarball ./dist/my-plugin-1.0.0.tar.gz
| Option | Description | Default |
|---|---|---|
--tarball | Existing plugin tarball | — |
--dir | Plugin directory used with --build | Current directory |
--build | Build the plugin before upload | false |
--registry | Marketplace base URL | https://marketplace.emdashcms.com |
--no-wait | Exit after upload without waiting for the processing result | false |
Provide --tarball, or pass --build to build from --dir first.
plugin login
Authenticate to the marketplace through GitHub device flow. --registry selects a different marketplace and defaults to https://marketplace.emdashcms.com.
npx emdash plugin login
plugin logout
Remove the saved marketplace credential. The optional --registry must identify the same marketplace used for login.
npx emdash plugin logout
emdash export-seed
Export database schema and content as a seed file. Works directly on a local SQLite file.
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 |
Output format
The exported seed file includes:
- Settings: Site title, tagline, social links
- Collections: All collection definitions with fields
- Taxonomies: Taxonomy definitions and terms
- Menus: Navigation menus with items
- Widget Areas: Widget areas and widgets
- Content (if requested): Entries with
$mediareferences and$ref:syntax for portability
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.
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. The same .env file is
read by Node and, in local development, by Wrangler and the Cloudflare Vite
plugin:
npx emdash secrets generate --write .env
--write refuses to overwrite an existing entry without --force.
Replacing a key in a deployment with existing encrypted data will leave
those secrets unreadable, so the protection is intentional.
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:
npx emdash secrets fingerprint emdash_enc_v1_...
emdash auth (deprecated)
auth secret
Generate a legacy EMDASH_AUTH_SECRET value:
npx emdash auth secret
Existing installations can keep this variable to preserve stable commenter-IP hashes. It does not encrypt plugin secrets.
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:
// 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.
.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:
{
"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 |
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:
{
"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.
| Code | Description |
|---|---|
0 | Success |
1 | Error (configuration, network, database) |