| name | wp-cli-patterns |
| description | WP-CLI-first principle — use WP-CLI instead of generating PHP code whenever possible, with command reference, ACF seeding patterns, and environment-aware execution |
| user-invocable | false |
| trigger | auto-invoke when .wp-create.json exists in project root |
WP-CLI Patterns — Best Practices for All Agents
This skill teaches the WP-CLI-first principle: use WP-CLI commands instead of generating PHP code whenever possible. WP-CLI saves tokens, reduces errors, and executes faster than writing throwaway PHP files.
Core Rule: WP-CLI Over PHP Generation
Always prefer a single WP-CLI command over generating PHP code.
# BAD (costs tokens): Generate PHP file with update_option()
# GOOD (1 line): $WP option update my_option 'value'
# BAD: Generate PHP with wp_insert_post()
# GOOD: $WP post create --post_type=page --post_title='About' --post_status=publish
# BAD: Generate PHP with wp_create_nav_menu()
# GOOD: $WP menu create "Primary EN" && $WP menu item add-post primary-en 5
# BAD: Generate PHP to activate a plugin
# GOOD: $WP plugin activate secure-custom-fields
# BAD: Generate PHP to set permalink structure
# GOOD: $WP rewrite structure '/%postname%/'
Use wp eval only when no dedicated WP-CLI subcommand exists for the operation (e.g., calling ACF's update_field() API).
The $WP Convention
Throughout all commands, agents, and skills, $WP is shorthand for the value of wp_cli.wrapper from .wp-create.json. Agents read this value and substitute it into all WP-CLI commands.
For example, if the environment is Docker:
docker exec my-project-wp wp --allow-root
$WP option update blogname "My Site"
docker exec my-project-wp wp --allow-root option update blogname "My Site"
How to read $WP: Parse .wp-create.json at the project root and extract the wp_cli.wrapper value. Every WP-CLI command in this skill assumes $WP is set to that value.
Environment-Aware Execution
Agents read wp_cli.wrapper from .wp-create.json and prepend it to all WP-CLI commands. The wrapper value depends on the environment type:
| Environment | wp_cli.wrapper value | Notes |
|---|
| Native | wp --path=/var/www/html/my-project | Direct CLI, requires WP-CLI installed on host |
| Docker | docker exec my-project-wp wp --allow-root | Executes inside WordPress container |
| DDEV | ddev wp | DDEV proxies to the web container |
| Lando | lando wp | Lando proxies to the appserver container |
| wp-env | npx wp-env run cli wp | wp-env proxies to its CLI container |
Important: Never hardcode the execution method. Always read the wrapper from the manifest so commands work across all environments.
Environment Detection: Reading .wp-create.json
The .wp-create.json manifest is generated by /wp-create at the project root. It is the single source of truth for all commands, agents, and skills.
if [ -f .wp-create.json ]; then
WP=$(jq -r '.wp_cli.wrapper' .wp-create.json)
PROJECT_SLUG=$(jq -r '.project.slug' .wp-create.json)
PRIMARY_LANG=$(jq -r '.languages.primary' .wp-create.json)
ADDITIONAL_LANGS=$(jq -r '.languages.additional[]' .wp-create.json)
ENV_TYPE=$(jq -r '.environment.type' .wp-create.json)
fi
When .wp-create.json does not exist, WP-CLI features are unavailable. Fall back to file-only operations (the pre-existing behavior).
WP-CLI Command Reference
All 16 domains agents should know. Every command below is prefixed with $WP in practice.
| Domain | Commands |
|---|
| Database | wp db create, wp db import, wp db export, wp db check, wp db query |
| Content | wp post create, wp post update, wp post delete, wp post meta update |
| Media | wp media import <url>, wp media regenerate |
| Options | wp option get, wp option update, wp option delete |
| Menus | wp menu create, wp menu item add-post, wp menu item add-custom, wp menu location assign |
| Plugins | wp plugin install, wp plugin activate, wp plugin deactivate, wp plugin list |
| Theme | wp theme activate, wp theme list |
| Config | wp config set, wp config get, wp config list |
| Rewrite | wp rewrite structure, wp rewrite flush |
| Cache | wp cache flush, wp transient delete --all |
| Cron | wp cron event list, wp cron event run |
| Search | wp search-replace 'old' 'new' |
| Scaffold | wp scaffold child-theme, wp scaffold plugin |
| Export/Import | wp export, wp import |
| User | wp user create, wp user update |
| Eval | wp eval 'php_code();' |
Useful Flags
--porcelain — return only the ID (useful for capturing post/attachment IDs)
--format=json — machine-readable output for parsing
--format=table — human-readable output for display
--allow-root — required inside Docker containers running as root
--force — skip confirmation prompts (e.g., wp post delete 1 --force)
ACF Field Seeding Patterns
Preferred: update_field() via wp eval
Use ACF's own API for field operations. This is storage-format-agnostic and handles field key registration, serialization, and caching correctly.
$WP eval "update_field('hero_title', 'Building Digital Excellence', 'option');"
ID=$($WP media import 'https://images.unsplash.com/photo-xxx' --title='Hero Background' --porcelain)
$WP eval "update_field('hero_image', $ID, 'option');"
$WP eval "
\$rows = array(
array('title' => 'Web Design', 'description' => 'Custom websites...', 'icon' => 43),
array('title' => 'SEO', 'description' => 'Search optimization...', 'icon' => 44),
);
update_field('services_cards', \$rows, 'option');
"
$WP eval "update_field('about_hero_title', 'Our Story', <post_id>);"
Alternative: Direct wp_options for Bulk Operations
Faster for bulk seeding but coupled to ACF internals. Use only when ACF API is unavailable or for bulk performance.
ACF stores options page fields in wp_options with an options_ prefix (e.g., field hero_title is stored as options_hero_title).
$WP option update options_hero_title "Building Digital Excellence"
$WP option update options_hero_image 42
$WP option update options_services_cards_0_title "Web Design"
$WP option update options_services_cards_0_icon 43
$WP option update options_services_cards_1_title "SEO"
$WP option update options_services_cards_1_icon 44
$WP option update options_services_cards 2
After Seeding: Always Flush Cache
$WP cache flush
Bilingual Naming Convention
This convention matches the i18n helper system defined in the wp-bilingual skill.
- Primary language fields use no suffix:
hero_title, hero_description, cta_text
- Secondary language fields append
_<lang>: hero_title_es, hero_description_es, cta_text_es
Seeding Bilingual Content
$WP eval "update_field('hero_title', 'Building Digital Excellence', 'option');"
$WP eval "update_field('hero_title_es', 'Construyendo Excelencia Digital', 'option');"
$WP eval "update_field('hero_subtitle_es', 'Creamos sitios web que funcionan', 'option');"
$WP eval "
\$rows = get_field('services_cards', 'option');
\$rows[0]['title_es'] = 'Diseno Web';
\$rows[1]['title_es'] = 'SEO';
update_field('services_cards', \$rows, 'option');
"
Rules
- Non-translatable fields (images, URLs, numbers, booleans) do NOT get language variants
- Read
.wp-create.json field languages.additional to know which suffixes to generate
- If
languages.additional is empty, skip all _<lang> field operations
Common Patterns
Create Pages and Set Front Page
HOME_ID=$($WP post create --post_type=page --post_title='Home' --post_status=publish --porcelain)
ABOUT_ID=$($WP post create --post_type=page --post_title='About' --post_status=publish --porcelain)
$WP option update show_on_front 'page'
$WP option update page_on_front $HOME_ID
Create and Assign Menus
$WP menu create "Primary EN"
$WP menu create "Primary ES"
$WP menu item add-post primary-en $HOME_ID --title="Home"
$WP menu item add-post primary-en $ABOUT_ID --title="About"
$WP menu item add-post primary-es $HOME_ID --title="Inicio"
$WP menu item add-post primary-es $ABOUT_ID --title="Acerca"
$WP menu location assign "Primary EN" primary_en
$WP menu location assign "Primary ES" primary_es
Import Media and Use Attachment ID
ID=$($WP media import 'https://example.com/photo.jpg' --title='Hero Image' --porcelain)
$WP eval "update_field('hero_image', $ID, 'option');"
Verify Operations
$WP eval "echo get_field('hero_title', 'option') ? 'OK' : 'EMPTY';"
$WP eval "echo get_page_template_slug($PAGE_ID);"
$WP plugin list --status=active --format=table
$WP menu location list --format=table
Final Cleanup After Seeding
$WP rewrite flush
$WP cache flush