Reference › CLI
Docs/Reference/CLI

CLI

What you'll learn

The two command-line tools: create-easy-cms to set up a project, and easy-cms for migrations, users, types, backups, scheduled jobs and the standalone server.

create-easy-cms

npm
npx create-easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]
pnpm
pnpm create easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]
yarn
yarn create easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]
bun
bun create easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]

Adds Easy CMS to a project:

  • In a Nuxt or Next.js project, it installs the packages, writes easy-cms.config.ts, a .env with a random EASY_CMS_SECRET, wires up the module or route handlers, and adds the database and uploads to .gitignore.
  • In a new or empty directory (or with --standalone), it sets up a standalone server for any frontend.
Option
dirWhere to set up. Default: the current directory.
--dbsqlite (a file) or postgres (PGlite locally, a server in production). Asks when not given; sqlite with --yes.
--pmnpm, pnpm, yarn or bun. Default: the project's (its packageManager field, then its lockfile), else the one you ran it with.
--standaloneA standalone server even inside a Nuxt or Next.js project.
--yesAccept the defaults without asking.
--skip-installOnly write files; run your package manager yourself.

It installs and prints the next steps with that package manager (pnpm exec easy-cms …, yarn easy-cms …, bunx easy-cms …). With Yarn 2 or later it writes .yarnrc.yml with nodeLinker: node-modules: Plug'n'Play is not supported. Bun installs the packages; the CMS still runs on Node.js.

See Getting started for what happens next.

easy-cms

Installed as a dev dependency (a regular dependency for standalone servers). Every command loads .env from the project root and reads easy-cms.config.ts.

npm
npx easy-cms <command> [--config <file>] [--cwd <dir>]
pnpm
pnpm exec easy-cms <command> [--config <file>] [--cwd <dir>]
yarn
yarn easy-cms <command> [--config <file>] [--cwd <dir>]
bun
bunx easy-cms <command> [--config <file>] [--cwd <dir>]
Command
migrateApply pending migrations.
migrate:create <name>Write a migration for config changes.
migrate:statusList migrations and whether they are applied.
admin:createCreate a user.
generate:typesWrite TypeScript types for other apps.
backup <file>Copy the database to a SQLite file while the CMS runs.
backup:decrypt <file>Decrypt a backup made with backups.encryptionKey.
copy --from <config>Copy all content into another database, e.g. SQLite to Postgres.
jobs:runRun due scheduled jobs and webhook retries once.
search:reindex [collection]Build the search index again from the database.
audit:resignAfter a new secret: sign the audit log with it.
serveRun the CMS as its own server.

Options for every command: --config <file> (default easy-cms.config.ts), --cwd <dir> (the project root; its .env is loaded) and --help. Commands exit non-zero on failure, so they work in CI and deploy scripts. Set DEBUG=1 to see stack traces. Built-in commands stop on an option they don't know.

admin:create and jobs:run were called create-admin and run-scheduled before 0.60. The old names still work.

migrate

npm
npx easy-cms migrate
pnpm
pnpm exec easy-cms migrate
yarn
yarn easy-cms migrate
bun
bunx easy-cms migrate

Applies every migration in easy-cms/migrations that the database has not run yet, in order. Each runs in its own transaction; a failure rolls it back and stops. Run it on every deploy, before the new version starts. See Migrations & deployment.

migrate:create

npm
npx easy-cms migrate:create add-author-bio
pnpm
pnpm exec easy-cms migrate:create add-author-bio
yarn
yarn easy-cms migrate:create add-author-bio
bun
bunx easy-cms migrate:create add-author-bio

Compares the config with the last migration and writes easy-cms/migrations/<timestamp>_<name>.sql (and a .json snapshot) when something changed. In a terminal it asks whether a field that disappeared was renamed, so data is kept instead of dropped. Review the SQL, then commit both files.

migrate:status

npm
npx easy-cms migrate:status
# ✓ applied  20260925091723_init
# • pending  20260928040614_seo
pnpm
pnpm exec easy-cms migrate:status
# ✓ applied  20260925091723_init
# • pending  20260928040614_seo
yarn
yarn easy-cms migrate:status
# ✓ applied  20260925091723_init
# • pending  20260928040614_seo
bun
bunx easy-cms migrate:status
# ✓ applied  20260925091723_init
# • pending  20260928040614_seo

admin:create

npm
npx easy-cms admin:create --email ann@example.com --name Ann
npx easy-cms admin:create --email bob@example.com --role editor
pnpm
pnpm exec easy-cms admin:create --email ann@example.com --name Ann
pnpm exec easy-cms admin:create --email bob@example.com --role editor
yarn
yarn easy-cms admin:create --email ann@example.com --name Ann
yarn easy-cms admin:create --email bob@example.com --role editor
bun
bunx easy-cms admin:create --email ann@example.com --name Ann
bunx easy-cms admin:create --email bob@example.com --role editor

Creates a user with the admin role (or --role). The password is asked for in the terminal; where there is no terminal (CI, containers), it is read from EASY_CMS_ADMIN_PASSWORD. Handy when the first admin can't be created in the browser, or to recover access.

generate:types

npm
npx easy-cms generate:types --out ../web/src/cms-types.ts
pnpm
pnpm exec easy-cms generate:types --out ../web/src/cms-types.ts
yarn
yarn easy-cms generate:types --out ../web/src/cms-types.ts
bun
bunx easy-cms generate:types --out ../web/src/cms-types.ts

Writes one interface per collection and global (default file easy-cms-types.ts). The file has no imports, so a frontend in another repository can use it. Apps that import the config don't need it: their types are inferred.

backup

npm
npx easy-cms backup backups/cms-2026-09-28.db
pnpm
pnpm exec easy-cms backup backups/cms-2026-09-28.db
yarn
yarn easy-cms backup backups/cms-2026-09-28.db
bun
bunx easy-cms backup backups/cms-2026-09-28.db

Copies the database to a new SQLite file while the CMS keeps running, as a consistent snapshot; a name ending in .gz is compressed, like the backups made from the admin. The file must not exist yet. Postgres needs backups: { sqlite } in the config (or use pg_dump). Uploads are not included: back up the uploads folder or bucket separately. See Backups.

backup:decrypt

npm
npx easy-cms backup:decrypt my-site-2026-10-04-0300.db.gz.enc [--out <file>]
pnpm
pnpm exec easy-cms backup:decrypt my-site-2026-10-04-0300.db.gz.enc [--out <file>]
yarn
yarn easy-cms backup:decrypt my-site-2026-10-04-0300.db.gz.enc [--out <file>]
bun
bunx easy-cms backup:decrypt my-site-2026-10-04-0300.db.gz.enc [--out <file>]

Decrypts a backup made with backups.encryptionKey (a .db.gz.enc file, e.g. copied from the backup bucket) into a .db.gz file, with the key from the config. The database isn't opened. Without --out, the file is written next to it without .enc; it must not exist yet.

copy

npm
npx easy-cms copy --from easy-cms.old.config.ts
pnpm
pnpm exec easy-cms copy --from easy-cms.old.config.ts
yarn
yarn easy-cms copy --from easy-cms.old.config.ts
bun
bunx easy-cms copy --from easy-cms.old.config.ts

Copies every document, version, user and global from the database of the --from config into the database of the project's config, for example from SQLite to Postgres (or back). Ids stay the same, so relationships, history and logins keep working.

  • Both configs need the same collections and fields: import the main config into the old one and change only db. Different fields are refused.
  • The target must be empty. In development its tables are created; in production run easy-cms migrate against it first.
  • Uploaded files are not copied; they stay in the uploads folder or bucket.
  • Stop writing to the source while copying, or copy from a backup.

See Move from SQLite to Postgres.

jobs:run

npm
npx easy-cms jobs:run
pnpm
pnpm exec easy-cms jobs:run
yarn
yarn easy-cms jobs:run
bun
bunx easy-cms jobs:run

Runs due scheduled publishes and unpublishes and retries failed webhook deliveries, once. Servers do this every minute on their own; use this command from a cron job where no server process keeps running (serverless).

search:reindex

npm
npx easy-cms search:reindex          # every collection with search
npx easy-cms search:reindex posts
pnpm
pnpm exec easy-cms search:reindex          # every collection with search
pnpm exec easy-cms search:reindex posts
yarn
yarn easy-cms search:reindex          # every collection with search
yarn easy-cms search:reindex posts
bun
bunx easy-cms search:reindex          # every collection with search
bunx easy-cms search:reindex posts

Builds the search index again from the published documents: after adding search to a collection or changing its fields, or when indexing failed. An in-memory index (memorySearch()) is built by the server when it starts, so there is nothing to do for it.

audit:resign

npm
npx easy-cms audit:resign
pnpm
pnpm exec easy-cms audit:resign
yarn
yarn easy-cms audit:resign
bun
bunx easy-cms audit:resign

After rotating the secret: signs the audit log entries signed with one of previousSecrets with the current secret, so they still verify once the old one is removed. Entries no secret verifies were changed in the database; they are left as they are, listed, and the command exits with 1. The run is recorded as an audit entry.

serve

npm
npx easy-cms serve --port 4000
npx easy-cms serve --watch          # development: reload on config changes
pnpm
pnpm exec easy-cms serve --port 4000
pnpm exec easy-cms serve --watch          # development: reload on config changes
yarn
yarn easy-cms serve --port 4000
yarn easy-cms serve --watch          # development: reload on config changes
bun
bunx easy-cms serve --port 4000
bunx easy-cms serve --watch          # development: reload on config changes

Runs Easy CMS without Nuxt or Next.js: the admin at /admin, the REST API at /api/cms, and /healthz and /readyz for load balancers.

Option
--port <n>Port. Default: PORT, then 4000.
--host <host>Interface to listen on. Default: HOST, then all interfaces.
--watchReload when the config or files it imports change.
--trust-proxyTrust X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host (for the CSRF origin check) from your reverse proxy.

In production (NODE_ENV=production) pending migrations stop the server from starting. See Standalone server.

Commands from plugins

Plugins can add commands, e.g. easy-cms nested:rebuild from the nested pages plugin. npx easy-cms --help for an unknown command lists the ones your config has. Your own config can add them too:

export default defineConfig({
  // …
  cliCommands: [
    {
      name: 'posts:count',
      description: 'Print how many posts there are',
      run: async ({ cms, args, log }) => log(String(await cms.count('posts'))),
    },
  ],
})

run gets the CMS (its schema as it is: run migrate first), the words after the command name (args), the options (flags) and log. Return a number to exit with it.

flags holds every option the CLI doesn't use itself (--config, --cwd and --help are its own), in camelCase. Values are strings, or true for an option without a value:

You typeflags
--dry-run{ dryRun: true }
--limit=5{ limit: '5' }

Next steps