WordPress Remote CLI (wpklx)
wpklx is a dynamic CLI for the WordPress REST API. It discovers routes at runtime from /wp-json — any resource registered by WordPress core or plugins becomes a CLI command automatically.
Install
curl -fsSL https://raw.githubusercontent.com/KLIXPERT-io/wpklx/main/install.sh | bash
Quick start
wpklx login # Interactive setup (host, username, app password)
wpklx discover # Fetch and cache API schema
wpklx routes # List all available commands
wpklx post list # List published posts
wpklx post get 42 # Get post by ID
wpklx post create --title "Hello World" # Create a new post
Syntax
wpklx [--profile <name> | @name] <resource> <action> [id] [--option value] [flags]
--profile <name>/-p <name>/@name— optional, selects a named site profile (e.g.,--profile staging,-p staging,@staging). All three forms are equivalent.<resource>— the WordPress resource (post, page, media, user, category, tag, comment, or any plugin resource)<action>— CRUD action or shortcut[id]— positional ID (alternative to--id <n>)[--option value]— resource-specific parameters from the API schema
Actions and shortcuts
| Action | Shortcut | HTTP | Description |
|----------|----------|---------|--------------------|
| list | ls | GET | List items |
| get | show | GET | Get single item |
| create | new | POST | Create item |
| update | edit | PUT/PATCH | Update item |
| delete | rm | DELETE | Delete item |
Built-in commands
wpklx login # Interactive site setup wizard
wpklx discover # Force-refresh API schema cache
wpklx routes # List all discovered routes
wpklx help # Global help
wpklx <resource> help # Resource-specific help with parameters
wpklx version # Print version
wpklx serialize # Convert HTML to WordPress block HTML (standalone)
wpklx markdown # Convert Markdown to WordPress block HTML (standalone)
wpklx ability list # List Abilities API capabilities (WordPress 6.9+)
wpklx ability help # Abilities API reference
Profile management
wpklx config ls # List all profiles
wpklx config show # Show active profile details
wpklx config show @staging # Show specific profile
wpklx config add <name> # Add new profile interactively
wpklx config rm <name> # Remove a profile
wpklx config default <name> # Set the default profile
wpklx config path # Print config file path in use
Global flags
Profile selection
--profile <name>, -p <name> Use a named profile from wpklx.config.yaml
@<name> Shorthand for --profile (e.g., @staging)
If omitted, the default profile is used (set via wpklx config default).
Note: @name and --profile cannot be used together — use one or the other.
Output flags
--format <table|json|yaml> Output format (default: table). Use json for scripting, yaml for readability, table for humans.
--fields <list|all> Comma-separated fields to include in output. Use "all" to show every field returned by the API.
--quiet Suppress all output except resource IDs. Useful for scripting: wpklx post list --quiet | xargs -I{} wpklx post rm {}
--verbose Print debug information including HTTP requests, timing, and config resolution.
--no-color Disable ANSI color codes in output.
Pagination flags
--per-page <n> Number of results per page (default: 20, max depends on site)
--page <n> Page number to retrieve (default: 1)
Content transformation flags (for create/update actions)
--serialize Convert --content value from raw HTML to WordPress block HTML before sending. Requires --content.
--markdown Convert --content value from Markdown to WordPress block HTML before sending. Requires --content.
--no-h1 Strip the first <h1> from converted content. Use with --serialize or --markdown.
Note: --serialize and --markdown are mutually exclusive.
Safe mode flag
--revision Save a local snapshot before update/delete. Safety net for destructive operations.
--rev <n> Select a specific revision when restoring (1=latest, default).
Other flags
--env <path> Load a custom .env file instead of the default .env in cwd.
--help, -h Show help
--version, -v Show version
Flags accept both --flag value and --flag=value syntax.
Namespace prefix
When plugins register resources with the same name, use a namespace prefix:
wpklx wpml:post list # WPML plugin's post resource
wpklx woocommerce:product list # WooCommerce products
wpklx myplugin:settings get # Custom plugin settings
Without a prefix, wp/v2 core routes are prioritized.
Abilities API (WordPress 6.9+)
Sites on WordPress 6.9 (or with the Abilities API plugin) expose registered
"abilities" under wp-abilities/v1 — machine-readable capabilities with JSON
Schema for their input and output. These routes are not CRUD, so wpklx exposes
them through a dedicated ability command rather than the discovered schema.
wpklx ability list # All abilities: name, label, category, annotations
wpklx ability list --category data-retrieval
wpklx ability list --quiet # One ability name per line
wpklx ability get my-plugin/get-site-info # Full definition incl. input/output schema
wpklx ability categories # List categories
wpklx ability category data-retrieval # Single category
wpklx ability run my-plugin/get-site-info # Execute
Running abilities
The /run endpoint's HTTP method depends on the ability's annotations. wpklx
fetches the definition and picks it automatically:
| Annotation | Method | Input transport |
| ------------------- | -------- | ------------------------------------- |
| readonly: true | GET | input[key]=value query params |
| destructive: true | DELETE | input[key]=value query params |
| otherwise | POST | {"input": ...} JSON body |
Add --method GET|POST|DELETE to force one and skip the lookup.
Input can be raw JSON or individual flags:
wpklx ability run my-plugin/get-user --input '{"user_id":1}'
wpklx ability run my-plugin/get-user --user_id 1
wpklx ability run my-plugin/update-option --option_name blogname --option_value "New Title"
cat input.json | wpklx ability run my-plugin/update-option --input -
Flag names are passed through verbatim — use the exact keys from the ability's
input_schema (--user_id, not --user-id). Values are coerced: true/false/
null, numbers and JSON objects/arrays are parsed; anything else stays a string.
Notes
- Results print as JSON by default (an ability's output follows an arbitrary
output_schema).wpklx ability getdefaults to YAML. Override with--format. - Every endpoint requires authentication, and each ability applies its own
permission_callbackon top. - Only abilities registered with
show_in_restappear over REST. - Inspect before you run — an ability may be annotated
destructive: true. - If every ability command 404s, the site likely predates WordPress 6.9; wpklx
says so explicitly. Confirm with
wpklx routes.
# Run every ability in a category
wpklx ability list --category data-retrieval --quiet | xargs -I{} wpklx ability run {}
Stdin piping
Explicit mapping with --flag -
echo "Hello World" | wpklx post create --content - --title "My Post"
cat draft.md | wpklx page update 12 --content -
cat photo.jpg | wpklx media upload --file - --title "Hero"
Bare pipe (auto-mapped)
When no --flag - is specified, stdin maps to a sensible default:
| Resource | Default parameter |
|-----------------|-------------------|
| post, page | content |
| comment | content |
| category, tag | description |
| media | file |
| other | content |
echo "Post body" | wpklx post create --title "Auto-mapped"
cat article.md | wpklx post create --title "From Markdown" --markdown
Stdin is only accepted for write actions (create, update). If the default parameter is already provided via CLI args, bare-pipe stdin is ignored.
Media uploads
wpklx media upload --file ./photo.jpg --title "Hero Image"
wpklx media upload --file ./doc.pdf --title "Report"
cat image.png | wpklx media upload --file - --title "Piped" --mime-type image/png
curl -s https://example.com/img.jpg | wpklx media upload --file - --title "Downloaded"
MIME type is auto-detected from the file extension. Use --mime-type to override when piping binary data.
Content transformation
Inline (with create/update)
# Convert Markdown content to WordPress blocks on the fly
wpklx post create --title "My Post" --content "# Hello\n\nParagraph" --markdown
# Convert HTML to blocks
wpklx post update 42 --content "<h2>Updated</h2><p>New content</p>" --serialize
# Strip the first H1 before converting
cat article.md | wpklx post create --title "Article" --markdown --no-h1
Standalone commands
# Convert an HTML file to block HTML
wpklx serialize --file article.html --output article.blocks.html
cat page.html | wpklx serialize --no-h1 > blocks.html
# Convert a Markdown file to block HTML
wpklx markdown --file draft.md --output draft.blocks.html
cat README.md | wpklx markdown > readme.blocks.html
Output formats
Table (default)
Auto-selects essential columns (id, title, slug, status, date). Use --fields to customize:
wpklx post list # Default columns
wpklx post list --fields=all # Every column
wpklx post list --fields=id,title,status,date # Specific columns
JSON
wpklx post list --format json
wpklx post get 42 --format json
YAML
wpklx post list --format yaml
Quiet (IDs only)
wpklx post list --quiet
# 1
# 42
# 103
Profiles
Switch between WordPress sites using --profile, -p, or @name:
# All three forms are equivalent:
wpklx --profile production post list
wpklx -p production post list
wpklx @production post list
# --profile / -p can appear anywhere in the command:
wpklx post list --profile staging --format json
wpklx post create -p staging --title "Test"
# More examples:
wpklx @local page ls --status draft
wpklx post list # Uses the default profile
Profiles are defined in wpklx.config.yaml (local) or ~/.config/wpklx/config.yaml (global).
Config resolution order: CLI flags > .env file > active YAML profile > built-in defaults.
Environment variables: WP_HOST, WP_USERNAME, WP_APPLICATION_PASSWORD, WP_API_PREFIX, WP_PER_PAGE, WP_TIMEOUT, WP_VERIFY_SSL, WP_OUTPUT_FORMAT.
Error handling
wpklx retries transient failures (network errors, 429, 502, 503, 504) with exponential backoff. It does not retry auth or validation errors.
When a resource or action is not found, wpklx suggests similar commands using fuzzy matching.
All error messages include what went wrong, why, and remediation steps. For example, authentication errors suggest checking credentials and regenerating application passwords, network errors differentiate between timeout/DNS/SSL/connection-refused with targeted fixes, and validation errors list each invalid field with its constraint.
Exit codes
| Code | Meaning | Common causes and fixes |
|------|----------------------|------------------------|
| 0 | Success | — |
| 1 | General error | Unexpected API response |
| 2 | Configuration error | Missing profile, bad YAML, missing required fields. Fix: wpklx config show or wpklx login |
| 3 | Authentication error | Wrong credentials, expired application password. Fix: regenerate at WP Admin → Users → Profile → Application Passwords |
| 4 | Resource not found | Unknown resource or missing item. Fix: wpklx routes to check available resources |
| 5 | Validation error | Missing required fields, invalid values. Fix: wpklx <resource> help to see accepted parameters |
| 6 | Network/timeout | Timeout, DNS failure, SSL error, connection refused. Fix: check URL with wpklx config show |
Safe Mode / Revisions
The --revision flag creates a local snapshot of a resource before any update or delete operation. Snapshots are stored in ~/.config/wpklx/revisions/ with up to 10 revisions kept per resource (oldest auto-pruned).
Pseudo-actions
Two local commands manage revisions (no API call needed):
wpklx <resource> revisions <id> # List saved snapshots for a resource
wpklx <resource> restore <id> # Restore the most recent snapshot
wpklx <resource> restore <id> --rev <n> # Restore a specific snapshot (1=latest)
Workflow example
# Update with safety net
wpklx post update 42 --title "New Title" --revision
# Oops — list snapshots
wpklx post revisions 42
# Restore previous state
wpklx post restore 42
# Or restore a specific revision
wpklx post restore 42 --rev 2
Delete with safety net
wpklx post delete 42 --revision
# Post is deleted, but a snapshot was saved first
wpklx post restore 42
# Post is re-created from the snapshot
Notes:
- Only fields accepted by the resource's update endpoint are restored (smart field filtering).
- Snapshots are profile-scoped — stored under
~/.config/wpklx/revisions/{profile}/{resource}/{id}/. --revisioncan be combined with any other flags on update/delete commands.
Edit workflow (pull / push / diff)
Edit a resource locally like a file, preview the changes, then push only what changed. Uses Google's diff-match-patch under the hood for readable text diffs.
Works for any resource that exposes both get and update (post, page, and most others).
Commands
wpklx <resource> pull <id> # Download to <resource>-<id>.json + hidden baseline sidecar
wpklx <resource> pull <id> --file <path> # Custom output path
wpklx <resource> diff --file <path> # Show local edits (vs baseline)
wpklx <resource> diff --file <path> --server # Also compare baseline to current server; flag conflicts
wpklx <resource> push --file <path> # Send only changed fields (with confirm prompt)
wpklx <resource> push --file <path> --dry-run # Preview diff without sending
wpklx <resource> push --file <path> --yes # Skip confirm prompt
wpklx <resource> push --file <path> --force # Skip conflict check AND confirm prompt
How it works
pull writes two files:
<resource>-<id>.json— user-facing working file (auto-managed fields like timestamps stripped for noise).<resource>-<id>.baseline.json— hidden sidecar holding the full server state at pull time
Both files embed a _wpklx metadata block (resource, id, profile, host, pulled_at) so diff/push resolve the target without extra flags. If the file is edited for a different resource than invoked, push aborts.
push diffs the working file against the baseline, sends only the changed fields (filtered to those accepted by the update endpoint), then refreshes the baseline from the server response so subsequent diffs/pushes stay accurate.
Conflict detection
If a field was edited locally AND on the server since the pull, push aborts with an error listing the conflicted fields. Options:
- Re-pull to get the latest baseline, redo your edits, and push again.
- Pass
--forceto overwrite the server's version.
Use wpklx <resource> diff --file <path> --server to preview server drift before pushing.
Example
wpklx post pull 42 # writes post-42.json and .post-42.baseline.json
# edit post-42.json in your editor
wpklx post diff --file post-42.json # preview changes
wpklx post push --file post-42.json # confirm and push
Notes
- Fields the update endpoint does not accept are skipped (and logged) rather than sent.
pullusescontext=editso content fields come back raw, not rendered.- Add
.*.baseline.jsonto.gitignoreif you check in working files. - Re-pulling overwrites the baseline — uncommitted edits in the working file can be lost, so commit or stash first.
Best practices
- Always run
wpklx discoverafter installing or removing WordPress plugins to refresh the route cache. - Use
wpklx <resource> helpto see all accepted parameters for a resource before constructing commands. - Use
--format jsonwhen you need to parse output or chain commands. - Use
--quietto get IDs for scripting (e.g., pipe into xargs). - Use
--verboseto debug authentication or network issues. - Use
--fields=allto inspect the full data shape before selecting specific fields. - Prefer positional IDs (
wpklx post get 42) over--id 42for brevity. - Use
--serializeor--markdownwith--no-h1when the first heading duplicates the post title. - Use
--revisionon update/delete commands to create a safety net. Restore withwpklx <resource> restore <id>. - For non-trivial edits to existing content, prefer the
pull→ edit →diff→pushworkflow over one-shotupdate— it gives a preview, surfaces server-side conflicts, and only sends changed fields.
Complex examples
Bulk publish all draft posts
wpklx post list --status draft --quiet | xargs -I {} wpklx post update {} --status publish
Create a post from a Markdown file with block serialization
cat article.md | wpklx post create \
--title "Complete Guide to TypeScript" \
--status draft \
--categories 5,12 \
--tags 8,15,23 \
--markdown \
--no-h1
Upload an image and set it as a post's featured image
MEDIA_ID=$(wpklx media upload --file ./hero.jpg --title "Hero Image" --quiet)
wpklx post update 42 --featured_media "$MEDIA_ID"
Mirror posts from production to staging
wpklx --profile production post list --format json --fields=title,content,status,categories \
| jq -c '.[]' \
| while read -r post; do
title=$(echo "$post" | jq -r '.title.rendered // .title')
content=$(echo "$post" | jq -r '.content.rendered // .content')
echo "$content" | wpklx -p staging post create --title "$title" --content - --status draft
done
Export all pages to individual JSON files
for id in $(wpklx page list --quiet --per-page 100); do
wpklx page get "$id" --format json > "page-${id}.json"
done
Batch delete all trashed posts
wpklx post list --status trash --quiet | xargs -I {} wpklx post delete {} --force
Create a page from an HTML file, serialized to blocks
wpklx page create \
--title "About Us" \
--content "$(cat about.html)" \
--serialize \
--no-h1 \
--status publish
Find posts by a specific author and re-assign them
wpklx post list --author 3 --quiet | xargs -I {} wpklx post update {} --author 7
List all WooCommerce products on sale with specific fields
wpklx woocommerce:product list --on_sale true --fields=id,name,price,sale_price --per-page 50
Multi-site content audit: compare post counts across profiles
echo "Production: $(wpklx -p production post list --quiet --per-page 1 2>/dev/null | wc -l) posts"
echo "Staging: $(wpklx -p staging post list --quiet --per-page 1 2>/dev/null | wc -l) posts"
Upload multiple images from a directory
for img in ./images/*.jpg; do
wpklx media upload --file "$img" --title "$(basename "$img" .jpg)" --quiet
done
Create a post with inline Markdown content
wpklx post create \
--title "Release Notes v2.0" \
--content "## What's New
- **Dark mode** — fully themed UI
- **Performance** — 3x faster page loads
- **API v2** — new endpoints for integrations
## Breaking Changes
The \`/v1/legacy\` endpoint has been removed. Migrate to \`/v2/modern\` before upgrading." \
--markdown \
--status draft
Chain discovery with route inspection for a new site
wpklx --profile newsite discover && wpklx --profile newsite routes
Pull, edit, and push a post safely
wpklx post pull 42
# open post-42.json, tweak title/content/status, save
wpklx post diff --file post-42.json --server # preview local edits + any server drift
wpklx post push --file post-42.json # sends only changed fields; aborts on conflict
Conditional update: only publish if post exists
if wpklx post get 42 --quiet 2>/dev/null; then
wpklx post update 42 --status publish
else
echo "Post 42 not found"
fi
Scan to join WeChat group