Audit WordPress performance and apply confirmed cleanup or configuration fixes through WP-CLI.
Documents
WordPress API Pro
Manage and audit WordPress content through guarded REST API and batch workflows.
What it does
Manage WordPress posts, pages, media, WooCommerce products, Elementor data, SEO metadata, and ACF or JetEngine fields through REST API scripts. Supports credentialed single- or multi-site workflows, CPT discovery and seeding, and dry-run-first batch changes. It also runs public, no-auth checks for PageSpeed, SSL, security headers, CMS/PHP signals, and basic SEO.
When to use it
- Drafting and updating WordPress content
- Dry-running changes across multiple sites
- Seeding ACF or JetEngine custom post types
- Running a no-auth pre-sale site audit
The skill document
WordPress API Pro
Manage WordPress sites through the REST API. Runs as an OpenClaw skill or in Claude Code.
Running in Claude Code
This skill runs the scripts/*.py directly. From the skill directory (~/.claude/skills/wordpress-api-pro/ after bash INSTALL.sh):
- Auth: export
WP_URL/WP_USERNAME/WP_APP_PASSWORD, or useconfig/sites.jsonfor multi-site. - Dependencies: the ACF / SEO / JetEngine / plugin-detection scripts need
requests(python3 -m pip install requests, ideally in a venv). The core post/page/media/WooCommerce/batch scripts use the stdlib only. - Local dev sites (e.g.
http://site.local) work — the private/HTTP restriction applies only to--allow-remote-urlmedia downloads, not the WP API base URL. - Pairs with the Elementor MCP kit (
claude-elementor-pro): build page structure with the MCP, then do media uploads, SEO meta, custom fields, and WooCommerce here.
Safety rules
- Never publish or update live content without explicit user approval. Confirm target site, IDs, fields, and status.
- Use least-privilege credentials. Prefer a dedicated WordPress user/application password scoped to the required role.
- Do not store production credentials in the repo. Use environment variables when possible.
- Protect config files. If you create
config/sites.json, keep it local, untracked, andchmod 600 config/sites.json. - Batch changes are dry-run by default. Add
--executeonly after reviewing the dry-run output. - Targeting every site is blocked by default. Add
--allow-allonly when the user explicitly approved all configured sites. - Local file reads are restricted.
--content-fileand media uploads can read only from the current working directory by default. SetWP_ALLOWED_FILE_ROOTSto opt into another safe directory. - Remote media URLs are opt-in.
upload_media.pyrequires--allow-remote-urlorWP_ALLOW_REMOTE_URLS=1, allows HTTPS only, and blocks private/local network hosts. - Raw SEO meta keys warn by default.
seo_meta.pyemits a stderr WARNING when writing a key not in the Rank Math / Yoast allowlist. SetWP_REQUIRE_ALLOWLIST=1to refuse instead. ACF/JetEngine custom-field keys are unaffected — arbitrary keys are their intended API. - Interactive publish confirmation on TTY.
create_post.pyandupdate_post.pyprompt for confirmation before--status publishwhen run interactively. Pass--yes/-yto bypass. Non-interactive/agent runs are unchanged.
Authentication
Recommended environment variables:
export WP_URL="https://example.com"
export WP_USERNAME="wp-api-user"
read -rs WP_APP_PASSWORD
export WP_APP_PASSWORD
Application Password setup:
- Open
https://your-site.example/wp-admin/profile.php. - Create a new Application Password for a dedicated API user.
- Copy it once and store it in a secret manager or environment variable.
- Rotate/revoke it when no longer needed.
Quick start
Read/list posts
python3 scripts/get_post.py --post-id 123
python3 scripts/list_posts.py --per-page 10 --status publish
Create a draft
python3 scripts/create_post.py \
--title "Draft title" \
--content "Draft content" \
--status draft
Update a post after approval
python3 scripts/update_post.py \
--post-id 123 \
--title "Approved title" \
--content "Approved content" \
--status draft
Read content from a local file safely
By default the file must be under the current working directory:
python3 scripts/update_post.py \
--post-id 123 \
--content-file ./content/post-123.html \
--status draft
To opt into another safe folder:
export WP_ALLOWED_FILE_ROOTS="/absolute/path/to/approved-content"
python3 scripts/update_post.py --post-id 123 --content-file /absolute/path/to/approved-content/post.html
Multi-site configuration
Copy the template locally:
cp config/sites.example.json config/sites.json
chmod 600 config/sites.json
Use a dedicated user per site and keep app_password values local only.
{
"sites": {
"sample-site": {
"url": "https://example.com",
"username": "wp-api-user",
"app_password": "",
"description": "Sample site; put the real credential only in local config/sites.json"
}
},
"groups": {
"sample": ["sample-site"]
}
}
CLI wrapper
./wp.sh --list-sites
./wp.sh sample-site get-post --id 123
./wp.sh sample-site update-post --id 123 --status draft
Group operations require an explicit flag:
./wp.sh sample --execute-group update-post --id 123 --status draft
If the group is named all, add --allow-all only after explicit approval:
./wp.sh all --execute-group --allow-all update-post --id 123 --status draft
Batch operations
Batch mode is dry-run unless --execute is present:
python3 scripts/batch_update.py \
--group sample \
--post-ids 123,456 \
--status draft
Apply after review:
python3 scripts/batch_update.py \
--group sample \
--post-ids 123,456 \
--status draft \
--execute
Targeting every site requires explicit opt-in:
python3 scripts/batch_update.py \
--group all \
--allow-all \
--post-ids 123 \
--status draft
Media upload
Local file upload, restricted to allowed file roots:
python3 scripts/upload_media.py \
--file ./media/image.jpg \
--title "Image title"
Remote URL upload, explicit opt-in and HTTPS-only:
python3 scripts/upload_media.py \
--file https://cdn.example.com/image.jpg \
--allow-remote-url \
--title "Image title"
Plugin integrations
scripts/detect_plugins.py— detect ACF, Rank Math, Yoast, JetEngine.scripts/acf_fields.py— read/write ACF fields.scripts/seo_meta.py— read/write Rank Math and Yoast SEO metadata.scripts/jetengine_fields.py— read/write JetEngine custom fields.scripts/site_audit.py— no-auth Tier-1 website audit (PageSpeed/SSL/security headers/CMS+PHP/SEO basics). Public probes only; run cold pre-sale.scripts/describe_cpt.py— discover a CPT's rest_base, taxonomies, and field keys (read-only).scripts/seed_content.py— batch-create CPT entries with ACF/Jet fields, taxonomies, and featured images from a JSON dataset. Dry-run by default; pass--executeto write.scripts/elementor_content.py— read/update Elementor_elementor_data.scripts/woo_products.py— manage WooCommerce products.
Seeding dynamic content (CPT)
For dynamic sites (JetEngine/ACF listings), populate the entries the listings render:
describe_cpt.py --post-type projects— learn the rest_base, taxonomies, field keys.- Write a JSON dataset (array of
{post_type, title, content, status, terms, featured_image, acf, jet}). seed_content.py --dataset data.json— review the dry-run plan (no writes, stdlib-only).seed_content.py --dataset data.json --execute— create (drafts by default).
Notes: the CPT, taxonomies, and ACF field-groups must already exist (admin-side).
featured_image accepts a media id or a URL/path (URL fetch needs --allow-remote-url).
--execute needs the requests dependency (used by the ACF/Jet writers). Re-running
creates duplicates (no upsert yet).
Verification before live writes
Before any live mutation:
- Confirm the site URL.
- Confirm post/page/product IDs.
- Confirm fields and status.
- Prefer
draftunless the user explicitly approvespublish. - Run dry-run for batch operations.
- Keep a backup/export for critical content.
Questions people ask
- What WordPress data can it read or change?
- It handles posts, pages, media, WooCommerce products, Elementor `_elementor_data`, Rank Math and Yoast metadata, and ACF or JetEngine fields. It can also inspect custom post types and seed CPT entries from JSON datasets.
- How does it reduce the risk of unintended live changes?
- Live writes require explicit approval of the site, IDs, fields, and status. Batch updates and CPT seeding default to dry runs, publishing prompts for confirmation in interactive terminals, and all-site targeting requires an explicit `--allow-all` flag.
- Can it audit a site without WordPress credentials?
- Yes. The no-auth Tier-1 audit uses public probes to check PageSpeed, SSL, security headers, CMS/PHP signals, and basic SEO; plugin and SEO-stack discovery is authenticated.
Related skills
Audit sites and previews, rank AEO fixes, validate schema, and gate regressions in CI.
Integrate and debug third-party REST and GraphQL APIs with service-specific references and client playbooks.
Writes and reviews PHP while tracing runtime, data-boundary, dependency, testing, and deployment failures.
Find technical SEO blockers and turn the evidence into scored, prioritized fixes.
Deploy and manage web apps through an HTTP API, with templates, status checks, logs, and version controls.