@mirkobozzetto/pcms-cli
v1.7.0
Published
Payload CMS CLI — Type-safe REST API client for Payload CMS
Downloads
236
Maintainers
Readme
pcms-cli
A CLI to control Payload CMS from the terminal.
I built this because I needed to create blog posts, push drafts, and manage content on my Payload instances without opening the admin panel. I use it mainly through Claude Code — I tell it what I want in plain language and it handles the rest. But it works just as well from a regular shell, a script, or any CI pipeline.
Quick examples
# Import a markdown article as a draft
pcms import ./article.md --collection posts
# List published posts
pcms documents list posts --where 'status[equals]=published' --sort '-publishedAt'
# Publish a draft
pcms publish posts 42
# Upload an image
pcms media upload ./hero.jpg --alt "Article cover"
# Export a post back to markdown
pcms export posts 42 -f md -o ./backup.mdClaude Code Skill
The repo includes a Claude Code skill (.claude/skills/). This is how I use pcms day-to-day — through natural language inside Claude Code:
/pcms create a draft post about AI hallucinations in legal tech
/pcms list my drafts
/pcms import all the markdown files in ./articles/
/pcms publish posts 42
/pcms how many published posts do I haveThe skill figures out the right pcms command, checks your auth, runs it, and formats the output. It ships with reference docs for every feature.
To install the skill after cloning:
ln -s /path/to/pcms-cli/.claude/skills ~/.claude/skills/pcmsInstall
npm install -g pcms-cli
# or
pnpm add -g pcms-cliFrom source:
git clone https://github.com/mirkobozzetto/pcms-cli.git
cd pcms-cli
pnpm install && pnpm build && pnpm link --globalNeeds Node.js 18+.
Setup
pcms auth login --domain https://your-site.com --email [email protected] --password 'yourpassword'
# Check it works
pcms collectionsIf you're in an interactive terminal, you can omit --password and it will be prompted with hidden input. From scripts or Claude Code, always pass --password explicitly.
Supports multiple instances — just pcms auth login again with a different domain. The last one becomes the default, or use --domain on any command.
Commands
Content lifecycle
The typical flow: create → review → publish.
# Create from a markdown file (best for articles)
pcms import ./article.md --collection posts --status draft
# Or create inline
pcms documents create posts --title "My Post" --status draft --excerpt "Short desc"
# Or from raw JSON
pcms documents create posts --data '{"title":"My Post","status":"draft","category":1}'
# Check your drafts
pcms documents list posts --where 'status[equals]=draft' --limit 10
# Read a specific post
pcms documents get posts 42
# Edit it
pcms documents update posts 42 --title "Better Title" --excerpt "Updated"
# Publish
pcms publish posts 42
# Changed your mind
pcms unpublish posts 42
# Delete (asks confirmation, use --force to skip)
pcms documents delete posts 42Media
pcms media upload ./image.jpg --alt "Description for accessibility"
# returns the media ID — use it in document creationSearch
pcms search "keyword" --limit 10Requires the Payload Search plugin.
Export / Import
# Export to markdown (with frontmatter)
pcms export posts 42 -f md -o ./post.md
# Export to JSON
pcms export posts 42 -f json -o ./post.json
# Import markdown with frontmatter
pcms import ./article.md --collection posts --status draft
# Batch import a folder
for f in ./articles/*.md; do pcms import "$f" --collection posts; doneMarkdown format for import:
---
title: 'My Article'
slug: 'my-article'
excerpt: 'Short description'
status: 'draft'
---
Content here. Supports headings, **bold**, _italic_, `code`, [links](url),
lists, code blocks, blockquotes — all converted to Payload's Lexical format.Collections info
pcms collections # list all
pcms collections schema posts # show fields and types
pcms collections count posts # total count
pcms collections count posts --where 'status[equals]=published'Versions
pcms versions posts 42 # list version history
pcms version posts <versionId> # inspect a version
pcms restore posts <versionId> # roll backGlobals
pcms globals # list globals
pcms globals navigation # read one
pcms global:update nav --data '{"items":[{"label":"Home","url":"/"}]}'Auth management
pcms auth status # current profile
pcms auth me # user details from the API
pcms auth profiles # all saved profiles
pcms auth logout # remove current profileWhere clause
Filter documents with Payload's query syntax:
pcms documents list posts --where 'status[equals]=published'
pcms documents list posts --where 'title[like]=juridique&status[not_equals]=draft'
pcms collections count posts --where 'publishedAt[exists]=true'| Operator | Example |
| -------------- | -------------------------- |
| equals | status[equals]=published |
| not_equals | status[not_equals]=draft |
| greater_than | views[greater_than]=100 |
| less_than | views[less_than]=50 |
| like | title[like]=keyword |
| exists | publishedAt[exists]=true |
| in | status[in]=draft,review |
Stack
- TypeScript 6 (strict, zero
any) - Commander.js
- Native fetch (Node 18+)
- Single runtime dependency
- Vitest 4 for tests
Development
pnpm install
pnpm build # compile to dist/
pnpm test # vitest
pnpm typecheck # tsc --noEmit
pnpm format # prettier
pnpm check # all of the aboveContributing
Open an issue first. Then fork, branch, pnpm check, PR.
Links
License
MIT — Mirko Bozzetto
