wp search-replace
Overview
Find and replace any string across all WordPress database tables — including inside PHP serialized data. The essential command for URL migrations, domain changes, and content updates.
What It Does
wp search-replace scans every row and column across all (or specified) WordPress tables, finds strings matching the search term, and replaces them with the replacement string. Critically, it handles PHP serialized data correctly — updating byte-length counts so serialized arrays and objects remain valid after replacement.
Syntax
wp search-replace <old-string> <new-string> [<table>...] [OPTIONS]
Arguments & Options
| Argument / Flag | Default | Description |
|---|---|---|
OLD_STRING | — | String to find |
NEW_STRING | — | String to replace with |
TABLE... | All tables | Specific tables to limit search to |
--dry-run | — | Preview what would change without writing |
--skip-columns=COLS | — | Skip specific columns (comma-separated) |
--include-columns=COLS | — | Only search in these columns |
--all-tables | — | Include all tables, not just with WP prefix |
--network | — | Run on all sites in a multisite network |
--precise | — | Use regex-capable matching (slower) |
--recurse-objects | — | Handle nested serialized PHP objects |
--no-report | — | Suppress results table output |
--report-changed-only | — | Only show tables where replacements were made |
--format=FORMAT | table | Output format: table, json, csv, yaml |
--export=FILE | — | Export SQL instead of writing to DB |
--export_insert_size=N | 50 | Number of inserts per SQL query when exporting |
Basic Usage
Domain migration (the most common use case)
wp search-replace 'http://old-domain.com' 'https://new-domain.com'
Always preview first with --dry-run
wp search-replace 'http://old-domain.com' 'https://new-domain.com' --dry-run
Dry-run output:
+------------------+-----------+--------------+------+
| Table | Column | Replacements | Type |
+------------------+-----------+--------------+------+
| wp_options | option_value | 3 | SQL |
| wp_posts | post_content | 47 | SQL |
| wp_postmeta | meta_value | 12 | PHP |
+------------------+-----------+--------------+------+
Success: 62 replacements to be made.
Skip the guid column (WordPress best practice)
wp search-replace 'http://old.com' 'https://new.com' --skip-columns=guid
Always skip the guid column
The guid column in wp_posts is used as a permanent unique identifier for posts. WordPress documentation recommends not changing it even during migrations.
Real-World Scenarios
Scenario 1: Production → Staging migration
# 1. Import production dump to staging
wp db import prod_dump.sql
# 2. Preview the replacements
wp search-replace 'https://example.com' 'https://staging.example.com' --dry-run --skip-columns=guid
# 3. Apply
wp search-replace 'https://example.com' 'https://staging.example.com' --skip-columns=guid
# 4. Flush cache
wp cache flush
Scenario 2: HTTP to HTTPS migration
# Dry run
wp search-replace 'http://example.com' 'https://example.com' --dry-run --skip-columns=guid
# Apply
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid
wp cache flush
Scenario 3: Change site path (moved to subdirectory)
wp search-replace 'https://example.com' 'https://example.com/blog' --skip-columns=guid
Scenario 4: Multisite Network search-replace
# Replace across all network sites
wp search-replace 'old-network.com' 'new-network.com' --network --skip-columns=guid
Scenario 5: Export replacements as SQL (without applying)
wp search-replace 'http://old.com' 'https://new.com' --export=migration.sql --skip-columns=guid
# Review migration.sql, then apply:
wp db import migration.sql
Best Practices
- Always run
--dry-runfirst — see exactly what will change before committing. - Always use
--skip-columns=guid— guid is a permanent post identifier. - Back up with
wp db exportbefore any search-replace on production. - Include protocol in strings — replace
http://example.com, not justexample.com, to avoid partial matches. - Flush cache after —
wp cache flushclears stale cached values. - Use
--report-changed-onlyto quickly identify which tables were affected.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Serialized data broken after manual SQL UPDATE | Used raw SQL instead of wp search-replace | Use wp search-replace — it fixes byte counts |
| URLs partially replaced | String included protocol but DB has mixed http/https | Run both: first http://, then https:// |
| Site still loads old domain | Object cache not cleared | Run wp cache flush |
| Command too slow on large DB | Many tables or large data | Add --table to limit scope; run during off-peak |
| Dry-run shows 0 replacements | Search string not in DB | Verify string with wp db query "SELECT ..." |
Quick Reference
wp search-replace 'http://old.com' 'https://new.com' --dry-run --skip-columns=guid
wp search-replace 'http://old.com' 'https://new.com' --skip-columns=guid
wp search-replace 'old' 'new' --network --skip-columns=guid
wp search-replace 'old' 'new' --export=changes.sql
wp cache flush
Next Steps
- Advanced Search-Replace — regex, object recursion, and edge-case techniques.
wp db export— back up before replacing.wp cache flush— clear cache after replacing.