CLI reference
Use the right Payload CMS CLI command for app setup, generated types, import maps, migrations, and wired block installs, with pnpm and npx examples.
Payload CMS projects use two command-line tools with similar names. The official Payload CLI creates projects, generates types and import maps, and manages database migrations. The community-built Payload Components CLI installs blocks and wires them into a Payload v3 project. It complements the Payload CLI; it does not replace it.
This guide maps each job to the right command, then documents every Payload Components command. Start with the table below if you arrived here looking for the Payload CMS CLI.
Which Payload CLI command should I run?
| Job | Command | Provided by |
|---|---|---|
| Create a Payload app | npx create-payload-app@latest | Payload |
| Generate TypeScript types | pnpm payload generate:types | Payload CLI |
| Generate the admin import map | pnpm payload generate:importmap | Payload CLI |
| Create a database migration | pnpm payload migrate:create | Payload CLI |
| Check migration status | pnpm payload migrate:status | Payload CLI |
| Install and wire a block | npx payload-components add hero-basic | Payload Components CLI |
| Preview a block install | npx payload-components add hero-basic --dry-run | Payload Components CLI |
| Diagnose block-install readiness | npx payload-components doctor | Payload Components CLI |
The pnpm payload ... examples assume your package.json has a payload script. If it
does not, run the binary through your package manager or add the script used by the
official starter. The commands after payload stay the same with npm, pnpm, or Yarn.
Payload Components is an independent MIT project. Its payload-components binary is
not part of the official Payload package. Use Payload's own CLI for config-derived work
such as types, import maps, and migrations. Use this project's CLI when you want to copy
a block and apply the collection, renderer, generated-type, and admin import-map wiring.
Use the official Payload CMS CLI
Create a new app
Payload's project initializer is the fastest route to a supported Payload v3 and Next.js app:
npx create-payload-app@latestChoose the website starter when you want an existing Pages collection, blocks renderer, and media setup. A blank app can also accept Payload Components, but it needs the starter scaffold before a block can be wired.
See Payload's installation documentation for the current runtime requirements and starter choices.
Generate TypeScript types
Run type generation after changing collections, globals, or block fields:
pnpm payload generate:typesPayload reads your config and updates the generated declaration file. If the CLI cannot
find the config, set PAYLOAD_CONFIG_PATH to its location for that command. The dedicated
Payload type-errors guide covers stale generated types,
config-path failures, and the checks to run before committing the output.
Payload's generated types documentation
is the source of truth for typescript.outputFile and config-specific behavior.
Generate the admin import map
Payload normally regenerates the import map while the dev server runs. Generate it directly after moving admin components, changing component paths, or repairing a stale map:
pnpm payload generate:importmapThe Payload import-map command guide explains the output, custom config paths, and common repair sequence. Payload's custom-components documentation defines how component paths become import-map entries.
Run database migrations
Migration commands belong to the official Payload CLI. Create a migration after a schema change, inspect what is pending, then apply it in the environment that owns the database:
pnpm payload migrate:create
pnpm payload migrate:status
pnpm payload migratePayload also provides rollback and reset commands. Those can destroy or replace local data, so read the adapter-specific behavior before running them. Payload's migration documentation lists the complete command set and the production workflow.
Use the Payload Components CLI
Run the package help command to see the available commands and flags:
npx payload-components --helpPayload Components commands accept --cwd <path> when the target project is not the
working directory. Commands that change files validate the project shape first. Planning
commands such as diff, doctor, and --dry-run do not write to the target project.
Install blocks
add accepts any number of component names and installs each through the full wrapper
pipeline, so one command can stand up a whole page:
npx payload-components add hero-basic faq-card pricing-cardsThe component catalog builds this command for you: tick components as you browse and copy the single command from the tray at the bottom.
Repeats are collapsed, and each name is installed exactly once. Because every stage is idempotent, a name that is already installed is a no-op rather than an error.
| Flag | Effect |
|---|---|
--dry-run | Print the plan — files, wiring, dependencies, post-install commands — and change nothing |
--demo | After a successful install, write the reviewable demo seed script |
--localized | Apply the block's explicit semantic localization policies |
Install from another registry
add also takes shadcn's namespaced form, so a block from someone else's registry installs
through the same command:
npx payload-components add @acme/heroThe scope must be configured in your components.json (or package.json) first:
{
"registries": {
"@acme": "https://acme.example/r/{name}.json"
}
}A third-party item is files only. It has no Payload Components manifest, so no
collection registration, renderer mapping, or type regeneration is applied, nothing is
recorded in .payload-components/state.json, and list / diff / update / remove do
not manage it. --demo and --localized are rejected for the same reason. Only https
registries are accepted.
Install a template
A template concept is a block set plus a page plan. add-template installs
and wires every block it composes, then prints which blocks each page uses:
npx payload-components templates
npx payload-components add-template saas-launchAdd --demo to also write one seed script per page of the concept:
npx payload-components add-template saas-launch --demoEach script creates a single draft Page holding that page's blocks, in the order the
page composes them, and never publishes it. Every page owns its own private ownership
record, so re-running one script updates only its own Page — the same adopt-or-refuse rules
a single-component seed follows.
Seeded blocks carry each block's own sample content, not the curated copy you see on the template preview — that copy lives in the site's demo data, not in Payload field shape. Treat the result as a wired skeleton to rewrite in the admin, which is why it lands as a draft. Each page also creates its own placeholder media, so an N-page template leaves N placeholder uploads behind.
It validates your project shape before installing anything, so an unsupported repo fails
on the first check rather than part-way through twenty blocks. --dry-run prints the block
list and page plan without touching the project.
Localize
Payload internationalization has two halves that have to agree, and either one alone does
nothing: the config declares which locales exist, and the block fields declare which text
stores one value per locale. localize sets both, across every block already installed:
npx payload-components localize --locales en,zhIt inserts a localization block into your buildConfig({ ... }) call — labelled in each
language, rtl where the script needs it — copies
src/blocks/shared/localizeFields.ts, wraps each installed block config's field list in
it, and records policy version semantic-v1 in install state so update re-applies the wrapper rather than
reinstalling a config without it.
| Flag | Effect |
|---|---|
--locales <codes> | Comma-separated language tags — en,zh, en,zh-TW,ja, en,pt-BR |
--default-locale <code> | The canonical locale; defaults to the first --locales entry |
--no-fallback | Write fallback: false, so an untranslated locale renders empty |
--dry-run | Print the whole plan and change nothing |
--force | Replace an existing localization block, and wrap locally edited configs |
Every step is idempotent, so re-run it after installing more blocks. Once the locales are declared, drop the flag and it wraps only what is new:
npx payload-components localizeNaming components narrows it to those:
npx payload-components localize hero-basic faq-cardA single block — or every block of a template — can also be installed localized in one step, which is the same field-level transform without the config half:
npx payload-components add hero-basic --localized
npx payload-components add-template saas-launch --localizedOn a template the locale notice is printed once for the run, not once per block.
localize refuses rather than guesses, exiting non-zero with the config or block left
exactly as you wrote it. That includes a config with no buildConfig({ ... }) call, a
localization value it cannot safely replace (such as a reference, assertion, or later
spread), and an existing direct localization block that declares something else
(--force replaces that last shape). A block config with local edits is skipped the same
way, because wrapping it would re-baseline your edit as pristine.
Turning on localization changes how Payload stores the affected fields. Payload does not
backfill existing values. Back up the database and explicitly migrate them into the default
locale before applying the schema change, and run --dry-run first.
See Internationalization for the full story — what
localizeFields marks and deliberately does not, querying a locale from the front end,
and translating the admin UI itself.
Maintain an install
A recorded install has a lifecycle. These four commands read and act on
.payload-components/state.json.
list
npx payload-components list
npx payload-components list --jsonJoins the registry catalog with what this project recorded: which components are installed, which are behind the version this CLI ships, and which recorded components no longer have a manifest. It works in any directory, including one that was never initialized.
diff
npx payload-components diff
npx payload-components diff hero-basic --jsonCompares each recorded install against the source baseline captured by its successful install and reports three kinds of drift: a version behind this CLI, a file whose content no longer matches, and wiring that has gone missing. Comparison is content-normalized, so line endings and a trailing newline are never reported as edits.
Current installs record normalized source hashes in .payload-components/state.json. Older
state is migrated automatically; the CLI carries the known pre-hash release baselines and
fails closed when it cannot prove one, instead of guessing whether an upstream change is a
local edit.
Mutating CLI invocations lock the target project for their full operation, including host-file
patches and state writes. A second add, update, remove, seed, or init against the same
project exits with the active command and PID instead of racing a read-modify-write step.
When a component is behind, diff also prints the changelog entries the upgrade would
apply, and the migration any breaking entry demands — 0.1.0 → 0.2.0 on its own is not a
decision anyone can make.
diff exits non-zero when anything has drifted, which makes it usable as a CI gate on
install drift.
update
npx payload-components update
npx payload-components update hero-basic --force
npx payload-components update --accept-localization-policy-changeRe-installs recorded components at the version this CLI ships. Canonical source replacements
and retired-file deletions are staged, then committed as one rollback-capable batch; add then
reconciles dependencies, wiring, generators, and state through its idempotent stages. A failed
upgrade no longer creates a window where the live component source is simply missing.
Local edits are protected. A component with a modified file is skipped, listed by name,
and the command exits non-zero. Pass --force to overwrite it, or copy your edits out
first. Run diff to see exactly which files are involved.
If an older recorded version has no reconstructable source baseline, it is protected the same
way: update skips it until you review the installed files and explicitly pass --force.
Content-breaking upgrades are held back. A component whose changelog marks a pending
version breaking is not installed at all — rewriting the files is the easy half, and you
still have to migrate the documents already in your database. update prints the migration
the manifest declares and exits non-zero until you pass --accept-breaking.
npx payload-components update --accept-breakingThese are separate decisions with separate flags: --force means "discard my local
edits", while --accept-breaking means "I have migrated the manifest's content change".
Legacy localized installs have a third guard. They used field-type inference, which could
localize URLs, prices, or form actions. After migrating those stored values back to global
fields, --accept-localization-policy-change adopts the explicit semantic-v1 policies.
The CLI updates source and state only; it never migrates the database.
With no names, update targets every component that is behind, left in a partial state, or
recorded as localized under the legacy policy. A current non-localized install is not rewritten
merely because older state predates the localization-policy field.
--dry-run prints the plan.
Changelogs
Each manifest carries its own release notes, newest first:
"changelog": [
{
"version": "0.2.0",
"summary": "Renamed the headline field.",
"breaking": true,
"dataMigration": "Rename the stored `heading` field to `title` before publishing."
},
{ "version": "0.1.0", "summary": "Initial release." }
]The registry validates this on every load: entries must be real semver, ordered newest
first, include the manifest's own version, and a breaking entry must say what to migrate —
otherwise update could refuse an upgrade without being able to tell you why. A component's
newest entry also appears on its docs page once it has moved past its first release.
remove
npx payload-components remove hero-basic --dry-run
npx payload-components remove hero-basic --accept-stored-content
npx payload-components remove hero-basic --force --accept-stored-contentThe inverse of an install: it deletes the files this component owns, unregisters the block, drops its imports, regenerates types and the import map, and removes its state record.
Owned source is protected. Removal refuses an exclusively owned file that changed after
installation. It also refuses matching source or wiring with no install record, an unknown
legacy baseline, or a retained orphan whose shared-file ownership cannot be resolved. Review
and copy out anything you need first; --force is the explicit instruction to accept that
destructive uncertainty.
Three things it deliberately does not do:
- Shared family files survive. A file another recorded component also ships is kept and
reported. Ownership comes from each component's recorded install-time file set, so removing
hero-basicwhilehero-videois installed leaves the shared hero field base in place even if a retained component's current manifest moved or disappeared. - Package dependencies stay. The CLI cannot know whether your own code adopted them.
- Stored content is untouched. Existing Page documents keep their block data. A real
removal therefore stops until you pass
--accept-stored-content, confirming that you migrated or deleted those blocks in the admin. This consent is independent from--force, which only accepts source-ownership risk.
Removal is idempotent, and a repeat run that changes nothing skips the generators instead of spending minutes regenerating for no effect.
Use it from a coding agent
npx payload-components mcpRuns a Model Context Protocol server over stdio, so an agent can search the catalog, read a component's install contract, and preview what an install would change — instead of guessing at component names.
{
"mcpServers": {
"payload-components": {
"command": "npx",
"args": ["-y", "payload-components", "mcp"]
}
}
}| Tool | Answers |
|---|---|
list_components, search_components | Which block fits, and is it already installed here |
get_component | Files, Payload wiring, supported majors, status in this project |
plan_install | Exactly what an install would change |
diff, doctor | What has drifted, and whether this project can accept installs |
list_templates, get_template | The block set and page plan behind a template concept |
Every tool is read-only. The server answers which block, and what will it change;
installing stays an explicit payload-components add run in your shell, where the change
is visible and approvable.
Contribute a component
pnpm payload-components new hero-splitThis is repository-only contributor tooling and is not part of the published consumer CLI. It scaffolds a whole component bundle in a checkout of this repository: the block source, manifest, registry item, doc page, and demo twin, plus entries in the demos registry, the docs sidebar, generated site-catalog projection, and README inventory table. Consumer CLI help derives its component list from the registry at runtime.
It then prints what it deliberately did not decide — its editorial entry in
src/lib/component-catalog.ts, its dbName abbreviation
with a uniqueness check against every existing block, the Content model prose, and the demo
sample content. See
the component template README
for the full workflow.
Set up and diagnose
npx payload-components init
npx payload-components doctor
npx payload-components doctor --json
npx payload-components seed hero-basicinit creates the components.json that registry installs need. doctor runs a read-only
health check — see Installation. seed writes the demo seed
script for an already-installed component.
doctor --json emits the same run as a structured report. Every finding carries a scope
(project, scripts, state, or a component name) so a CI job can group results without
parsing prose, and the exit code separates the two failures that need different responses:
2 means the project itself cannot accept installs, 1 means the project is fine but a
recorded install has drifted.
Supported project shapes
Installs are gated on a recognised project shape. Two are supported, matched in order:
| Target | Shape |
|---|---|
payload-website-starter | The official Payload website starter layout: src/blocks/RenderBlocks.tsx and src/collections/Pages/index.ts |
payload-blocks-app | The same page-blocks shape at non-starter paths — a flat Pages.ts, a renderer under src/components, or a repo without src/ |
A target declares candidate paths per file plus the anchors the patcher needs, so a file at
the right path with the wrong shape is not a match. doctor prints which files it resolved.
Starting from a bare Payload app
A project scaffolded by create-payload-app has none of the pieces an install needs: no
blocks renderer, no Pages collection with a layout field, and none of the primitives
installed blocks import. init --scaffold lays that base down:
npx payload-components init --scaffoldIt copies eight files and registers two collections in your Payload config:
| File | Why it is needed |
|---|---|
src/utilities/ui.ts | cn() — imported by 69 of the shipped source files |
src/fields/link.ts, src/fields/linkGroup.ts | the link field blocks compose for their calls to action |
src/components/Link/index.tsx | CMSLink, which renders a stored link |
src/components/Media/index.tsx | Media, which renders an upload |
src/collections/Media.ts | the media collection those uploads live in |
src/collections/Pages/index.ts | the layout blocks field installs register into |
src/blocks/RenderBlocks.tsx | the renderer map installs wire into |
The result is the official starter's shape, which is why a scaffolded project then detects
as payload-website-starter rather than as a target of its own. The CLI records only files
whose bytes came from this scaffold. Matching starter files are adopted; custom existing
implementations stay unowned. A later init --scaffold updates pristine managed files and
reports local edits, while init --scaffold --force explicitly replaces those managed edits.
Set up Tailwind and shadcn first. Every installed block is Tailwind-classed, and
shadcn init refuses to run without a Tailwind configuration — which the blank
create-payload-app template does not ship. Install Tailwind, add
@import 'tailwindcss' to your frontend stylesheet, then run this command.
Your own code is never overwritten. A project that already has its own cn, Media, or
Pages collection keeps it as an unowned file the scaffold will not touch. A rerun can still
create any other missing canonical managed files. For files the scaffold already owns, it
updates pristine copies and reports edited copies rather than replacing them unless you pass
--force. If your Payload config has no readable collections: [ array, the command prints
the two lines to add rather than rewriting a buildConfig call it could not parse. If
shadcn init finishes without writing a components.json, the command stops there rather
than scaffolding on top of a project that still cannot install anything.
These files are a starting point, not a framework. Files created or adopted by the
scaffold are lifecycle-managed: later runs update pristine copies and preserve local
edits unless you pass --force. Custom implementations that did not match the scaffold
stay yours and unowned. The managed files are intentionally thinner than the official
Payload website starter's versions (no Button dependency in CMSLink, no image-size
variants in Media). If you are starting a content site from scratch, the website starter
itself is still the better base.
Exit codes
| Command | Exits non-zero when |
|---|---|
add | Any stage fails; partial state is recorded for a safe retry |
diff | Any inspected component has drifted |
update | A component was skipped for local edits, or held back as breaking |
localize | It left the Payload config or a block config untouched |
doctor | 1 a recorded install needs attention · 2 the project cannot accept installs |