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
npx create-easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]pnpm create easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]yarn create easy-cms [dir] [--db sqlite|postgres] [--pm npm|pnpm|yarn|bun] [--standalone] [--yes] [--skip-install]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.envwith a randomEASY_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 | |
|---|---|
dir | Where to set up. Default: the current directory. |
--db | sqlite (a file) or postgres (PGlite locally, a server in production). Asks when not given; sqlite with --yes. |
--pm | npm, pnpm, yarn or bun. Default: the project's (its packageManager field, then its lockfile), else the one you ran it with. |
--standalone | A standalone server even inside a Nuxt or Next.js project. |
--yes | Accept the defaults without asking. |
--skip-install | Only 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.
npx easy-cms <command> [--config <file>] [--cwd <dir>]pnpm exec easy-cms <command> [--config <file>] [--cwd <dir>]yarn easy-cms <command> [--config <file>] [--cwd <dir>]bunx easy-cms <command> [--config <file>] [--cwd <dir>]| Command | |
|---|---|
migrate | Apply pending migrations. |
migrate:create <name> | Write a migration for config changes. |
migrate:status | List migrations and whether they are applied. |
create-admin | Create a user. |
generate:types | Write TypeScript types for other apps. |
backup <file> | Copy the SQLite database while the CMS runs. |
copy --from <config> | Copy all content into another database, e.g. SQLite to Postgres. |
run-scheduled | Run due scheduled jobs and webhook retries once. |
serve | Run 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.
migrate
npx easy-cms migratepnpm exec easy-cms migrateyarn easy-cms migratebunx easy-cms migrateApplies 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
npx easy-cms migrate:create add-author-biopnpm exec easy-cms migrate:create add-author-bioyarn easy-cms migrate:create add-author-biobunx easy-cms migrate:create add-author-bioCompares 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
npx easy-cms migrate:status
# ✓ applied 20260925091723_init
# • pending 20260928040614_seopnpm exec easy-cms migrate:status
# ✓ applied 20260925091723_init
# • pending 20260928040614_seoyarn easy-cms migrate:status
# ✓ applied 20260925091723_init
# • pending 20260928040614_seobunx easy-cms migrate:status
# ✓ applied 20260925091723_init
# • pending 20260928040614_seocreate-admin
npx easy-cms create-admin --email ann@example.com --name Ann
npx easy-cms create-admin --email bob@example.com --role editorpnpm exec easy-cms create-admin --email ann@example.com --name Ann
pnpm exec easy-cms create-admin --email bob@example.com --role editoryarn easy-cms create-admin --email ann@example.com --name Ann
yarn easy-cms create-admin --email bob@example.com --role editorbunx easy-cms create-admin --email ann@example.com --name Ann
bunx easy-cms create-admin --email bob@example.com --role editorCreates 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
npx easy-cms generate:types --out ../web/src/cms-types.tspnpm exec easy-cms generate:types --out ../web/src/cms-types.tsyarn easy-cms generate:types --out ../web/src/cms-types.tsbunx easy-cms generate:types --out ../web/src/cms-types.tsWrites 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
npx easy-cms backup backups/cms-2026-09-28.dbpnpm exec easy-cms backup backups/cms-2026-09-28.dbyarn easy-cms backup backups/cms-2026-09-28.dbbunx easy-cms backup backups/cms-2026-09-28.dbCopies the SQLite database to a new file while the CMS keeps running, as a consistent snapshot. The file must not exist yet. Uploads are not included: back up the uploads folder or bucket separately. For Postgres use pg_dump. See Backups.
copy
npx easy-cms copy --from easy-cms.old.config.tspnpm exec easy-cms copy --from easy-cms.old.config.tsyarn easy-cms copy --from easy-cms.old.config.tsbunx easy-cms copy --from easy-cms.old.config.tsCopies 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 migrateagainst 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.
run-scheduled
npx easy-cms run-scheduledpnpm exec easy-cms run-scheduledyarn easy-cms run-scheduledbunx easy-cms run-scheduledRuns 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).
serve
npx easy-cms serve --port 4000
npx easy-cms serve --watch # development: reload on config changespnpm exec easy-cms serve --port 4000
pnpm exec easy-cms serve --watch # development: reload on config changesyarn easy-cms serve --port 4000
yarn easy-cms serve --watch # development: reload on config changesbunx easy-cms serve --port 4000
bunx easy-cms serve --watch # development: reload on config changesRuns Easy CMS without Nuxt or Next.js: the admin at /admin, the REST API at /api/cms, and /healthz for load balancers.
| Option | |
|---|---|
--port <n> | Port. Default: PORT, then 4000. |
--host <host> | Interface to listen on. Default: HOST, then all interfaces. |
--watch | Reload when the config or files it imports change. |
--trust-proxy | Trust X-Forwarded-For and X-Forwarded-Proto 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({
// …
commands: [
{
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, and log. Return a number to exit with it.
Next steps
- Migrations & deployment: when to run which command.
- Standalone server: run the CMS for any frontend.