Skip to main content

wp search-replace — Advanced

Overview

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 is much slower

--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_tokens meta
  • 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
Multisite and --url

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

FlagUse Case
--dry-runAlways preview first
--skip-columns=guidPreserve posts' permanent identifiers
--preciseRegex-based pattern matching
--recurse-objectsDeep nested serialized object support
--networkRun across all multisite sites
--include-columns=COLSNarrow search to specific columns
--report-changed-onlyShorter output — only show changed tables
--export=FILEGenerate SQL without writing to DB
--all-tablesInclude non-prefixed tables

Best Practices

  1. Never use raw SQL UPDATE for string replacement — it breaks serialized data.
  2. Use --export for sensitive production changes — review SQL before applying.
  3. Use --include-columns to minimize the blast radius on large databases.
  4. Use --recurse-objects when replacing strings in complex plugin data structures.
  5. Flush and verify after every replacement — wp cache flush then check the site.

Troubleshooting

ProblemCauseFix
Theme/plugin settings lost after replaceSerialized data corrupted by partial replacementRestore from --export backup; re-run with --recurse-objects
--precise never finishesTable too large for row-by-row PHP processingLimit with --include-columns or target specific tables
Multisite — only first site replaced--network not usedAdd --network flag
Some values not replacedNested serializationAdd --recurse-objects

Next Steps