wp search-replace — Advanced
Beyond basic URL migrations, wp search-replace supports regex matching, recursive PHP object traversal, per-column targeting, multisite-wide replacements, and SQL-only export mode. This page covers the power-user patterns.
Prerequisites
Read the standard wp search-replace guide first. This page assumes you are familiar with the basic syntax and --dry-run workflow.
The --precise Flag: Regex-Backed Matching
By default, wp search-replace uses PHP str_replace() — fast but literal. With --precise, it uses preg_replace(), enabling pattern-based matching.
# Replace http:// but not https:// links
wp search-replace '/(^|[^s])http:\/\/example\.com/' '${1}https://example.com' --precise
--precise cannot use SQL-level filtering and must load every row into PHP for inspection. Use it only when literal matching is insufficient.
The --recurse-objects Flag: Nested Serialized Objects
WordPress stores complex data as serialized PHP. Sometimes objects contain other objects. By default, wp search-replace handles one level of serialization. --recurse-objects enables deep traversal.
wp search-replace 'old.com' 'new.com' --recurse-objects --skip-columns=guid
When to use:
- WooCommerce
_payment_tokensmeta - Complex ACF nested field structures
- Page builder (Bricks, Elementor) serialized JSON-in-PHP data
Target Specific Tables and Columns
Limit to a single table
wp search-replace 'old.com' 'new.com' wp_options
Limit to specific tables (space-separated)
wp search-replace 'old.com' 'new.com' wp_posts wp_postmeta
Limit to specific columns only
wp search-replace 'old.com' 'new.com' --include-columns=post_content,post_excerpt
Skip volatile columns
# Skip guid, post_password, and any session storage columns
wp search-replace 'old.com' 'new.com' \
--skip-columns=guid,post_password,session_tokens
Multisite Network Replacements
Replace across all sites in a multisite network
wp search-replace 'old-network.com' 'new-network.com' \
--network \
--skip-columns=guid \
--dry-run
Then apply:
wp search-replace 'old-network.com' 'new-network.com' \
--network \
--skip-columns=guid
When running on individual sub-sites within a network, use --url=http://sub.site.com to target the correct site's tables.
Export Mode: SQL File Without Applying
Generate SQL instead of writing to the live database. Review it, then apply manually:
wp search-replace 'http://old.com' 'https://new.com' \
--skip-columns=guid \
--export=migration_patch.sql
Review the file:
head -50 migration_patch.sql
wc -l migration_patch.sql
Apply when ready:
wp db import migration_patch.sql
Staged Migration Pattern (Safest Approach)
# 1. Backup
wp db export "pre_migration_$(date +%Y%m%d%H%M).sql"
# 2. Dry run — review ALL changes
wp search-replace 'https://old.com' 'https://new.com' \
--skip-columns=guid \
--report-changed-only \
--dry-run
# 3. Export SQL only (do not apply yet)
wp search-replace 'https://old.com' 'https://new.com' \
--skip-columns=guid \
--export=patch.sql
# 4. Review patch.sql
grep -c "UPDATE" patch.sql
# 5. Apply
wp db import patch.sql
# 6. Flush cache
wp cache flush
# 7. Verify
wp option get siteurl
wp option get home
Real-World Advanced Scenarios
Scenario 1: Fix broken serialized data from manual SQL replace
# If someone ran a raw SQL UPDATE and broke serialized data:
# 1. Restore from backup (preferred)
wp db import backup_before_sql_replace.sql
# 2. Then use wp search-replace properly
wp search-replace 'http://old.com' 'https://new.com' --skip-columns=guid
Scenario 2: Replace image CDN URL across all content
wp search-replace \
'https://old-cdn.example.com/wp-content/uploads/' \
'https://new-cdn.example.com/wp-content/uploads/' \
--skip-columns=guid \
--include-columns=post_content,post_excerpt,meta_value
Scenario 3: Replace after table prefix change
# After changing table prefix from wp_ to mySite_
# Only affects options table meta references
wp search-replace 'wp_' 'mySite_' wp_options \
--include-columns=option_value \
--dry-run
Scenario 4: Replace email domain across all users
wp search-replace 'user@olddomain.com' 'user@newdomain.com' wp_users \
--include-columns=user_email \
--dry-run
wp search-replace '@olddomain.com' '@newdomain.com' wp_users \
--include-columns=user_email
Common Flags Quick Reference
| Flag | Use Case |
|---|---|
--dry-run | Always preview first |
--skip-columns=guid | Preserve posts' permanent identifiers |
--precise | Regex-based pattern matching |
--recurse-objects | Deep nested serialized object support |
--network | Run across all multisite sites |
--include-columns=COLS | Narrow search to specific columns |
--report-changed-only | Shorter output — only show changed tables |
--export=FILE | Generate SQL without writing to DB |
--all-tables | Include non-prefixed tables |
Best Practices
- Never use raw SQL UPDATE for string replacement — it breaks serialized data.
- Use
--exportfor sensitive production changes — review SQL before applying. - Use
--include-columnsto minimize the blast radius on large databases. - Use
--recurse-objectswhen replacing strings in complex plugin data structures. - Flush and verify after every replacement —
wp cache flushthen check the site.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Theme/plugin settings lost after replace | Serialized data corrupted by partial replacement | Restore from --export backup; re-run with --recurse-objects |
--precise never finishes | Table too large for row-by-row PHP processing | Limit with --include-columns or target specific tables |
| Multisite — only first site replaced | --network not used | Add --network flag |
| Some values not replaced | Nested serialization | Add --recurse-objects |
Next Steps
- Standard Search-Replace Guide — basic patterns and migration workflow.
wp db export— always back up before replacing.wp cache flush— clear cache after replacement.