WordPress API Pro
Manage WordPress sites through the REST API from an OpenClaw skill.
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.
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/elementor_content.py— read/update Elementor_elementor_data.scripts/woo_products.py— manage WooCommerce products.
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.