Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Payload CLI

The Payload CLI lets you work with your project from a terminal. You can use it to generate types, run migrations, inspect your setup, run jobs, and more.

The CLI loads the Payload Config from your project. Run commands from your project directory, or use PAYLOAD_CONFIG_PATH if your config is somewhere else.

The examples on this page use pnpm, but you can also use npx payload or the equivalent command for your package manager.

1
pnpm payload --help

Built-in commands

Payload includes the following commands by default.

Core commands

Command

What it does

build

Generate the Admin Panel import map and TypeScript types, then run the project build.

generate:db-schema

Generate a database schema. Not every database adapter supports this command.

generate:importmap

Generate the import map used by the Admin Panel.

generate:types

Generate TypeScript types from your Payload Config.

help [command]

Show help for all commands or for one command.

info

Print Node.js, package, operating system, memory, and CPU information.

jobs:handle-schedules

Put jobs whose schedule is due into the queue.

jobs:run

Run jobs from the queue.

run <script-path> [args...]

Run a local script in the Payload environment.

Migration commands

Command

What it does

migrate

Run all pending migrations.

migrate:create [name]

Create a migration.

migrate:down

Roll back the latest migration batch.

migrate:fresh

Clear the database and run all migrations again.

migrate:refresh

Roll back completed migrations and run them again.

migrate:reset

Roll back all migrations.

migrate:status

Show which migrations have and have not run.

Use --help to see the arguments and options for a command:

1
pnpm payload migrate:create --help
2
pnpm payload jobs:run --help

JSON output, help, and input

Every command accepts the global --json option. It returns a predictable response that scripts and coding agents can read:

1
pnpm payload generate:types --json
1
{
2
"command": "generate:types",
3
"result": {
4
"outputFile": "/project/src/payload-types.ts"
5
},
6
"success": true
7
}

You can place --json before or after the command. Commands that do not return data omit result. A failed command returns success: false, an error code, and an error message. Input validation errors also include the issues and the command's input schema.

In JSON mode, stdout contains exactly one JSON response while logs and other diagnostics stream to stderr. Parse stdout and display or store stderr separately. Do not use 2>&1: merging the streams makes stdout invalid JSON. Stderr intentionally remains a streaming text channel rather than a second buffered JSON value.

Set PAYLOAD_CLI_JSON to use JSON output for every command without repeatedly passing --json. This is useful when a script or coding agent runs several commands:

1
export PAYLOAD_CLI_JSON=1
2
pnpm payload generate:types
3
pnpm payload migrate:status

Unset the variable to return to regular terminal output, or use --no-json to override it for one command:

1
PAYLOAD_CLI_JSON=1 pnpm payload info --no-json

Payload can return help as JSON. This is useful for scripts and coding agents that need to know which commands exist and what input they accept.

1
# Describe every command
2
pnpm payload help --json
3
4
# Describe one command
5
pnpm payload help migrate:create --json

For example, you would normally pass the migration name as an argument:

1
pnpm payload migrate:create add-authors

You can pass the same input as JSON with --input:

1
pnpm payload migrate:create --input '{"migrationName":"add-authors"}'

To read the input from a file, create migration-input.json:

1
{
2
"migrationName": "add-authors"
3
}

Then add @ before the file path:

1
pnpm payload migrate:create --input @migration-input.json

To read the same JSON from stdin, pipe it into the command and use -:

1
echo '{"migrationName":"add-authors"}' | pnpm payload migrate:create --input -

The JSON is validated in the same way as normal command arguments and options. Do not combine --input with other command input.

Custom commands

Use defineCLICommand to add your own command. Built-in and custom commands use the same API, help system, and input validation.

Define a command

The following example adds a seed command. Its code lives in seed.ts next to payload.config.ts:

1
import { strictObject, z } from 'payload'
2
import { defineCLICommand } from 'payload/cli'
3
4
export const seedCommand = defineCLICommand({
5
description: 'Seed the database.',
6
examples: ['payload seed', 'payload seed --clear'],
7
input: strictObject({
8
clear: z
9
._default(z.boolean(), false)
10
.check(z.describe('Delete existing documents first.')),
11
}),
12
handler: async ({ args, getPayload }) => {
13
const payload = await getPayload()
14
15
if (args.clear) {
16
// Delete existing documents...
17
}
18
19
await payload.create({
20
collection: 'pages',
21
overrideAccess: true,
22
data: { title: 'My page' },
23
})
24
25
payload.logger.info('Successfully seeded!')
26
27
return { result: { seeded: true } }
28
},
29
})

The input schema defines the values the command accepts. Payload uses it to type args, validate input, and generate help. Add examples when a complete command makes the arguments easier to understand. Payload includes them in both terminal and JSON help.

The z export from payload is Zod Mini. Payload already uses Zod Mini, so this export adds no extra schema code to the bundle. Use strictObject for the root input schema so Payload can also generate JSON help.

Payload does not export regular Zod because its larger API would increase the bundle size. If you prefer regular Zod's chaining API, add zod to your own project and import it directly:

1
pnpm add zod
1
import { z } from 'zod'

You can also use another schema library. Its schema must support both Standard Schema validation and Standard JSON Schema output.

Call getPayload() only when the command needs the Local API. Payload closes the instance when the command finishes.

Returning { result } makes that value available in JSON mode through --json or PAYLOAD_CLI_JSON. Return a number to set only the process exit code, or return { exitCode, result } when you need both. Payload sends payload.logger.* and console.* output to stderr in JSON mode. Use isJSON only when the command should behave differently in JSON mode.

Register the command

Add the command to cli.commands in your Payload Config. The map key becomes the command name:

1
export default buildConfig({
2
cli: {
3
commands: {
4
seed: './seed.js#seedCommand',
5
},
6
},
7
})

Command paths use the same format as other PayloadComponent paths. A path without # uses the default export. Add #exportName to use a named export, or use the object form:

1
cli: {
2
commands: {
3
seed: './seed.js',
4
cleanup: './cleanup.js#cleanupCommand',
5
importData: {
6
path: './data.js',
7
exportName: 'importDataCommand',
8
},
9
},
10
}

You can also pass a command directly. This example defines a small command inside the Payload Config:

1
import { buildConfig } from 'payload'
2
import { strictObject } from 'payload'
3
import { defineCLICommand } from 'payload/cli'
4
5
export default buildConfig({
6
cli: {
7
commands: {
8
hello: defineCLICommand({
9
description: 'Print a greeting.',
10
input: strictObject({}),
11
handler: ({ isJSON }) => {
12
if (!isJSON) {
13
console.log('Hello!')
14
}
15
16
return { result: { message: 'Hello!' } }
17
},
18
}),
19
},
20
},
21
})

Both forms work, but import paths are recommended. A direct command and all of its imports are loaded with the Payload Config, even when you are not using the CLI. An import path is only loaded when the CLI starts, so command-only dependencies stay out of the Payload Config bundle. Relative paths start from the folder containing payload.config.ts.

You can now run the seed command like any built-in command:

1
pnpm payload seed
2
pnpm payload seed --clear
3
pnpm payload seed --help

Arguments and options

By default, each field in input becomes a command option. Payload changes camelCase field names to kebab-case flags. For example, migrationName becomes --migration-name.

Use cli when a field needs different command-line behavior. For example, this makes filePath a positional argument:

1
defineCLICommand({
2
input: strictObject({
3
filePath: z.string().check(z.describe('File to import.')),
4
}),
5
cli: {
6
filePath: 'argument',
7
},
8
// ...
9
})

Set a field to false to accept it only through --input. For less common cases, you can use an object to change the flag, argument position, or parser.

Add, replace, or disable commands

A new key in cli.commands adds a command. Using the name of a built-in command replaces it. Setting a command to false disables it:

1
export default buildConfig({
2
cli: {
3
commands: {
4
seed: './seed.js#seedCommand',
5
'generate:types': './generateTypes.js#generateTypesCommand',
6
info: false,
7
},
8
},
9
})

After Payload loads the config, config.cli.commands shows the final list of built-in and custom commands.

Set cli itself to false to disable the entire Payload CLI:

1
export default buildConfig({
2
cli: false,
3
})

Working with data from the CLI

Payload includes local commands for inspecting the config and working with collections and globals. They use the Local API and load the current Payload config on every invocation.

getCollectionSchema and getGlobalSchema also return their LLM instructions alongside schema. With --json, these are available as result.instructions. Read them before creating or updating content.

Start by discovering the available slugs and inspecting the input schema for a collection:

1
pnpm payload getConfigInfo
2
pnpm payload getCollectionSchema --slug posts

You can then query and update data without starting the development server:

1
pnpm payload findDocuments \
2
--slug posts \
3
--where '{"status":{"equals":"published"}}'
4
5
pnpm payload updateDocument \
6
--slug posts \
7
--id 1 \
8
--data '{"title":"Updated title"}'

To explicitly enforce access control:

1
pnpm payload findDocuments --slug posts --override-access false

The same option is named overrideAccess in JSON input:

1
pnpm payload findDocuments --input '{"slug":"posts","overrideAccess":false}'

createDocuments accepts a JSON array, keeping each document's data and optional upload file together:

1
pnpm payload createDocuments \
2
--slug media \
3
--documents '[{"data":{"alt":"Company logo"},"file":"./images/logo.png"}]'

Upload input can also use an external URL. Payload applies the upload collection's pasteURL, allow-list, safe-fetch, header, and file-size settings before creating or updating the document:

The trusted local CLI does not require an explicit allow-list. If one is configured, both the initial URL and redirects must match it. pasteURL: false disables URL uploads.

1
pnpm payload createDocuments \
2
--slug media \
3
--documents '[{"data":{"alt":"Company logo"},"file":{"source":"externalURL","url":"https://example.com/logo.png"}}]'

For updateDocument, pass the same object through --file:

1
pnpm payload updateDocument \
2
--slug media \
3
--id 1 \
4
--data '{"alt":"Updated logo"}' \
5
--file '{"source":"externalURL","url":"https://example.com/logo.png"}'

For longer input, put the array in a file:

1
[{ "data": { "title": "First post" } }, { "data": { "title": "Second post" } }]
1
pnpm payload createDocuments --slug posts --documents @documents.json

You can instead pass the command input through --input. Explicit arguments and options override values from the JSON input:

1
{
2
"slug": "posts",
3
"documents": [
4
{ "data": { "title": "First post" } },
5
{ "data": { "title": "Second post" } }
6
]
7
}
1
pnpm payload createDocuments --input @create-posts.json

createDocuments and updateDocument return affected document IDs by default. Pass --returning when you need the complete documents:

1
pnpm payload updateDocument \
2
--slug posts \
3
--id 1 \
4
--data '{"title":"Updated title"}' \
5
--returning

Use --select with --returning to choose which fields come back:

1
pnpm payload updateDocument \
2
--slug posts \
3
--id 1 \
4
--data '{"title":"Updated title"}' \
5
--returning \
6
--select '{"title":true}'

The built-in data commands cover collection queries, counts, distinct values, create, update, delete, duplicate, and version operations. Globals have matching schema, read, update, count-version, find-version, and restore-version commands. Use payload help --json to list them or payload help <command> --json to inspect the exact input accepted by one command.

Running commands on a schedule

Use the global --cron option to run a command on a cron schedule:

1
pnpm payload jobs:run --cron "*/5 * * * *"

This example runs queued jobs every five minutes. The CLI process stays open while the schedule is active.

Was this page helpful?

Next

LLM Instructions