If you manage WordPress sites, you’ll eventually need to find and replace strings in your database. Maybe you’re migrating domains, switching from HTTP to HTTPS, or pushing a staging site to production. The wp search-replace command handles all of this — and it does it correctly, even with serialized data that would break if you used raw SQL queries.
This guide covers everything from basic syntax to advanced regex patterns, with real commands you can copy and adapt for your own projects. Every example here comes from actual migration and maintenance scenarios.
What WP-CLI Search-Replace Actually Does
The wp search-replace command searches through your WordPress database and replaces one string with another. What makes it essential is its ability to handle PHP serialized data — something a direct SQL UPDATE statement cannot do without corrupting your database.
WordPress stores serialized arrays and objects throughout its database, particularly in the wp_options table, widget settings, and post meta. These serialized strings include byte counts. Change a URL length without updating the byte count and the data becomes unreadable. WP-CLI handles this automatically by unserializing the data, making the replacement, and re-serializing it with correct byte counts. If you’re not familiar with WordPress database structure, our guide on the different types of WordPress default tables explains what each table stores and why it matters.
According to the official WP-CLI documentation, the command searches through all rows in a selection of tables and replaces appearances of the first string with the second string. It will correctly handle serialized values and will not change primary key values.
Basic Syntax and Core Options
The fundamental syntax is straightforward:
wp search-replace 'old-string' 'new-string' [table...] [--dry-run] [--precise] [--all-tables]
The first argument is the string you want to find. The second is the replacement. Optionally, you can specify which tables to search. Without specifying tables, WP-CLI searches all tables registered to the current WordPress installation (those matching the configured table prefix).
Here are the most important flags you should know:
--dry-run— Shows what would change without making any modifications. Always run this first.--precise— Forces the use of PHP serialization handling for all columns. Slower but more thorough.--all-tables— Searches all database tables, not just those with your WordPress prefix.--all-tables-with-prefix— Searches all tables that share the WordPress table prefix.--verbose— Prints detailed information about each replacement.--format=count— Outputs just the replacement count for scripting.
The Golden Rule: Always Dry-Run First
Before running any search-replace operation on a production database, you must use --dry-run. This is non-negotiable. The dry-run flag simulates the operation and tells you exactly how many replacements would occur in each table, without modifying a single row.
wp search-replace 'oldsite.com' 'newsite.com' --dry-run
The output looks like this:
+---------------------+-----------------------+--------------+------+
| Table | Column | Replacements | Type |
+---------------------+-----------------------+--------------+------+
| wp_commentmeta | meta_value | 0 | SQL |
| wp_comments | comment_author_url | 2 | SQL |
| wp_comments | comment_content | 5 | SQL |
| wp_options | option_value | 47 | PHP |
| wp_postmeta | meta_value | 12 | PHP |
| wp_posts | post_content | 156 | SQL |
| wp_posts | guid | 89 | SQL |
+---------------------+-----------------------+--------------+------+
Success: 311 replacements to be made.
Notice the “Type” column. “PHP” means the data is serialized and WP-CLI will handle it properly. “SQL” means a direct string replacement. Review the numbers carefully — if something looks off (like zero replacements when you expected many), your search string might be wrong.
Real Example 1: Domain Migration
Domain migration is the most common use case. You’re moving a site from one domain to another and every URL in the database needs to update. Here’s the proper sequence:
# Step 1: Back up the database
wp db export backup-before-migration.sql
# Step 2: Dry-run to see what changes
wp search-replace 'https://oldsite.com' 'https://newsite.com' --dry-run
# Step 3: Execute the replacement
wp search-replace 'https://oldsite.com' 'https://newsite.com'
# Step 4: Also replace without protocol (catches edge cases)
wp search-replace '//oldsite.com' '//newsite.com' --dry-run
wp search-replace '//oldsite.com' '//newsite.com'
# Step 5: Flush caches
wp cache flush
wp rewrite flush
Running the replacement in two passes — first with the full protocol, then with protocol-relative URLs — catches references stored in different formats. Some plugins store URLs with // prefixes, and email templates sometimes reference the bare domain.
John Blackbourn, a WordPress Core committer and developer of the Query Monitor plugin, has noted that “running search-replace without a backup is like editing production code without version control — you’re one mistake away from a very bad day.” Always export your database before any bulk operation.
Real Example 2: HTTP to HTTPS Migration
After installing an SSL certificate, you need to update all internal URLs from http:// to https://. This is one of the cleanest use cases for search-replace:
# Back up first
wp db export backup-before-ssl.sql
# Preview changes
wp search-replace 'http://yoursite.com' 'https://yoursite.com' --dry-run
# Execute
wp search-replace 'http://yoursite.com' 'https://yoursite.com'
# Flush everything
wp cache flush
wp rewrite flush
After running this, verify your site loads correctly over HTTPS. Check for mixed content warnings in the browser console — if any remain, run a more aggressive search:
# Catch any remaining http references
wp search-replace 'http://yoursite.com' 'https://yoursite.com' --all-tables --dry-run
The --all-tables flag extends the search beyond WordPress core tables to include tables created by plugins that might not use the standard table prefix.
Real Example 3: Staging to Production
Pushing a staging site to production requires replacing the staging URL with the live URL. This scenario often involves multiple replacements because staging environments sometimes have different directory structures. If you need help setting up a local staging environment first, check out our tutorial on how to install WordPress on localhost.
# Back up production database first
wp db export production-backup.sql --path=/var/www/production
# Import the staging database to production
wp db import staging-export.sql --path=/var/www/production
# Replace staging URL with production URL
wp search-replace 'https://staging.yoursite.com' 'https://yoursite.com' --path=/var/www/production
# If staging uses a subdirectory
wp search-replace 'https://yoursite.com/staging' 'https://yoursite.com' --path=/var/www/production
# Replace file paths if server paths differ
wp search-replace '/home/staging/public_html' '/home/production/public_html' --path=/var/www/production
# Flush
wp cache flush --path=/var/www/production
wp rewrite flush --path=/var/www/production
The --path flag tells WP-CLI where to find the WordPress installation. This is critical when you’re managing multiple environments from a single server or when running commands remotely.
Real Example 4: Fixing Broken Image URLs
After migrating a site or changing your uploads directory structure, image URLs can break across hundreds of posts. Instead of manually editing each post, use search-replace to fix them all at once:
# CDN migration — old URLs to new CDN
wp search-replace 'https://yoursite.com/wp-content/uploads' 'https://cdn.yoursite.com/wp-content/uploads' wp_posts wp_postmeta --dry-run
# Fix year/month directory changes
wp search-replace '/uploads/2024/01/' '/uploads/2025/01/' wp_posts --dry-run
# Update image references after changing upload directory
wp search-replace '/wp-content/uploads/' '/wp-content/media/' wp_posts wp_postmeta --dry-run
Notice that these commands target specific tables (wp_posts and wp_postmeta). When you know exactly where the broken references live, targeting specific tables speeds up the operation and reduces risk.
Handling Serialized Data Correctly
Serialized data is the primary reason you should use WP-CLI instead of raw SQL for search-replace operations. Here’s what serialized data looks like in the database:
a:2:{s:4:"logo";s:45:"https://oldsite.com/wp-content/uploads/logo.png";s:5:"title";s:7:"My Site";}
See the s:45? That’s a byte count. If you change oldsite.com to newsite.com using a raw SQL query, the URL length changes but the byte count stays at 45. WordPress then can’t unserialize the data, and the value is lost.
WP-CLI unserializes the data first, makes the replacement, then re-serializes it with the correct byte count:
a:2:{s:4:"logo";s:45:"https://newsite.com/wp-content/uploads/logo.png";s:5:"title";s:7:"My Site";}
This is transparent to you as the user — WP-CLI handles it automatically. But if you ever see “PHP” in the Type column of your dry-run output, you know serialized data is being handled correctly.
For deeply nested serialized data, add the --precise flag:
wp search-replace 'oldsite.com' 'newsite.com' --precise --dry-run
The --precise flag processes every column using PHP (un)serialization rather than relying on a faster SQL-based approach for non-serialized data. It’s slower but catches edge cases where serialized data might be stored in unexpected columns.
Targeting Specific Tables
You don’t always want to search-replace across the entire database. Targeting specific tables is faster, safer, and gives you more control:
# Only search wp_posts and wp_postmeta
wp search-replace 'old-text' 'new-text' wp_posts wp_postmeta
# Only search the options table
wp search-replace 'old-value' 'new-value' wp_options
# Only search WooCommerce order tables
wp search-replace 'old-sku' 'new-sku' wp_wc_orders wp_wc_order_items
You can list multiple table names after the replacement string. This is particularly useful when you know a change only affects post content (not options or user meta), or when you want to update plugin-specific tables without touching the rest of the database.
To see which tables exist in your database:
wp db tables
This lists every table, making it easy to identify the exact tables you need to target.
Advanced: Regex Mode
For pattern-based replacements, WP-CLI supports PCRE regular expressions with the --regex flag. This opens up powerful possibilities:
# Replace all subdomains with a single domain
wp search-replace '(https?://)([a-z]+)\.oldsite\.com' '$1newsite.com' --regex --dry-run
# Remove tracking parameters from URLs
wp search-replace '(\?|&)utm_[a-z]+=[^&"\s]*' '' --regex wp_posts --dry-run
# Normalize www and non-www
wp search-replace 'https?://(?:www\.)?yoursite\.com' 'https://yoursite.com' --regex --dry-run
Regex mode uses PCRE (Perl Compatible Regular Expressions) syntax. Capture groups work with $1, $2, etc. in the replacement string. Be extra careful with regex replacements — always dry-run first and review the replacement count carefully. A wrong pattern can corrupt content across your entire database.
One important caveat: regex mode is significantly slower than standard mode because every value must be processed through PHP’s regex engine. On large databases (100K+ rows), consider targeting specific tables to keep execution time reasonable.
Advanced: Skip Columns and Tables
Sometimes you need to exclude certain columns from the search-replace operation. The --skip-columns flag handles this:
# Skip the guid column (usually recommended)
wp search-replace 'oldsite.com' 'newsite.com' --skip-columns=guid
# Skip multiple columns
wp search-replace 'oldsite.com' 'newsite.com' --skip-columns=guid,user_pass
# Skip specific tables entirely
wp search-replace 'oldsite.com' 'newsite.com' --skip-tables=wp_users,wp_usermeta
The guid column in wp_posts deserves special attention. GUIDs are meant to be permanent identifiers. The WordPress Codex explicitly states that GUIDs should not be changed, as they’re used by RSS feeds to track which posts have been read. However, during a full domain migration, leaving old URLs in the GUID column is generally harmless — RSS readers might re-show some posts, but that’s typically the lesser concern.
The general recommendation from WordPress VIP and experienced developers: skip the GUID column unless you have a specific reason to change it.
Advanced: Export and Import Workflow
For complex migrations or when you want to test replacements without touching the live database, use an export/import workflow:
# Export the database
wp db export original.sql
# Run search-replace with --export flag
wp search-replace 'oldsite.com' 'newsite.com' --export=modified.sql
# Review the SQL diff if needed
diff original.sql modified.sql | head -100
# Import the modified database when ready
wp db import modified.sql
The --export flag outputs a new SQL file with all replacements applied, without modifying the current database. This is perfect for testing migrations locally before applying them to production, or for creating migration-ready database dumps.
Common Pitfalls and How to Avoid Them
After running thousands of search-replace operations across WordPress sites, these are the mistakes that catch people most often:
1. Not Backing Up First
This is the number one mistake. Always run wp db export before any search-replace operation. Database exports are fast and cheap. Recovering from a botched replacement without a backup is painful and sometimes impossible.
# Make it a habit — backup before every operation
wp db export backup-$(date +%Y%m%d-%H%M%S).sql
2. Forgetting About Serialized Data
If someone tells you to “just run a SQL query” to replace URLs, stop them. A raw UPDATE wp_options SET option_value = REPLACE(option_value, 'old', 'new') will break serialized data. Always use WP-CLI or a tool that handles serialization. This matters especially when dealing with the wp_options table, which stores heavily serialized data for widgets, theme settings, and plugin configurations. For a deeper look at what each table stores, see our breakdown of WordPress default database tables.
3. Wrong Table Prefix
If your site uses a custom table prefix (like site1_ instead of wp_), make sure WP-CLI is reading the correct wp-config.php. If you target wp_posts but your table is actually site1_posts, WP-CLI will report zero replacements and you’ll think nothing needed changing.
# Check your table prefix
wp config get table_prefix
# List actual tables
wp db tables
4. Partial String Matches
Searching for site.com when you mean oldsite.com will also match newsite.com, anothersite.com, and any other domain ending in site.com. Be as specific as possible with your search string:
# Too broad — will match unintended strings
wp search-replace 'site.com' 'newsite.com' --dry-run
# Better — specific enough to avoid false matches
wp search-replace 'https://oldsite.com' 'https://newsite.com' --dry-run
5. Skipping the Dry Run
It only takes an extra 10 seconds to run --dry-run first. Those 10 seconds can save you hours of recovery work. If the dry-run shows unexpected numbers (too many or too few replacements), investigate before proceeding.
6. Not Flushing Caches After
WordPress, object caches (Redis/Memcached), and page caches (Varnish, CDNs) all store copies of your data. After a search-replace operation, flush everything:
wp cache flush
wp rewrite flush
wp transient delete --all
Multisite Considerations
On WordPress Multisite installations, search-replace has additional considerations. Each sub-site has its own set of tables, and you need to decide whether to run the replacement network-wide or per-site:
# Replace across the entire network
wp search-replace 'oldnetwork.com' 'newnetwork.com' --network --dry-run
# Replace on a specific sub-site only
wp search-replace 'oldsite.com' 'newsite.com' --url=oldsite.com --dry-run
# Replace only in the main site's tables
wp search-replace 'old.com' 'new.com' --url=network.com --dry-run
The --network flag iterates through all sub-sites. The --url flag targets a specific sub-site by its URL. For large networks, consider running replacements per-site to monitor progress and catch issues early.
Performance Tips for Large Databases
On databases with millions of rows, search-replace can take a long time. Here are proven strategies to speed things up:
- Target specific tables — Don’t search the entire database if you know the change only affects
wp_posts. - Use
--skip-columns— Skip columns that can’t possibly contain the target string (like integer columns or datetime columns). - Increase PHP memory — For very large tables, you might need:
wp search-replace --memory_limit=512M - Run during low-traffic hours — Search-replace creates row locks. On a live site, run during your lowest-traffic window.
- Consider
--report-changed-only— Reduces output noise by only showing tables where changes were made.
# Optimized large-database replacement
wp search-replace 'oldsite.com' 'newsite.com' \
wp_posts wp_postmeta wp_options wp_comments wp_commentmeta \
--skip-columns=guid \
--report-changed-only \
--precise
Scripting and Automation
WP-CLI search-replace integrates well into deployment scripts and CI/CD pipelines. Here’s a practical migration script:
#!/bin/bash
# WordPress migration script
set -e
SOURCE_URL="https://staging.example.com"
TARGET_URL="https://example.com"
SOURCE_PATH="/home/staging/public_html"
TARGET_PATH="/home/production/public_html"
BACKUP_DIR="/home/backups"
# Create timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "Starting migration: $SOURCE_URL -> $TARGET_URL"
# Backup production database
wp db export "$BACKUP_DIR/pre-migration-$TIMESTAMP.sql" --path=$TARGET_PATH
echo "Backup created: pre-migration-$TIMESTAMP.sql"
# Export staging database
wp db export /tmp/staging-export.sql --path=$SOURCE_PATH
# Import to production
wp db import /tmp/staging-export.sql --path=$TARGET_PATH
# Run replacements
wp search-replace "$SOURCE_URL" "$TARGET_URL" --path=$TARGET_PATH --skip-columns=guid
wp search-replace "$SOURCE_PATH" "$TARGET_PATH" --path=$TARGET_PATH
# Flush caches
wp cache flush --path=$TARGET_PATH
wp rewrite flush --path=$TARGET_PATH
wp transient delete --all --path=$TARGET_PATH
echo "Migration complete. Verify at: $TARGET_URL"
echo "Rollback: wp db import $BACKUP_DIR/pre-migration-$TIMESTAMP.sql --path=$TARGET_PATH"
Using set -e at the top ensures the script stops immediately if any command fails — critical for migration scripts where a failed step means subsequent steps will cause problems. When building scripts that interact with your database, understanding SQL tools for WordPress can help you verify data integrity before and after migrations.
WP-CLI Search-Replace Cheat Sheet
Bookmark this table for quick reference. These are the commands and flags you will use most often:
| Command / Flag | What It Does | When to Use |
|---|---|---|
wp search-replace 'old' 'new' | Replace in all WP-prefix tables | Standard domain migration |
--dry-run | Preview without changing data | Always run first |
--precise | PHP serialization for all columns | When serialized data might be missed |
--all-tables | Search every table in the database | Plugin tables with non-standard prefixes |
--all-tables-with-prefix | All tables matching WP prefix | Multisite or shared-database setups |
--skip-columns=guid | Exclude specific columns | Avoid changing post GUIDs |
--skip-tables=wp_users | Exclude specific tables | Protect sensitive tables |
--regex | Enable PCRE regex patterns | Pattern-based replacements |
--export=file.sql | Write changes to SQL file | Test before applying to live DB |
--network | Run across all Multisite sub-sites | Network-wide URL changes |
--url=site.com | Target specific Multisite sub-site | Per-site replacement |
--verbose | Show each replacement | Debugging unexpected results |
--report-changed-only | Only show tables with changes | Cleaner output on large databases |
--format=count | Output replacement count only | Scripting and CI/CD pipelines |
wp db export backup.sql | Export database to SQL file | Before every search-replace |
wp cache flush | Clear WordPress object cache | After every search-replace |
Final Thoughts
The wp search-replace command is one of the most powerful tools in the WordPress developer’s toolkit. It handles the complexity of serialized data, gives you dry-run safety, supports regex for complex patterns, and integrates cleanly into automation scripts.
The workflow for any search-replace operation should always be: backup, dry-run, review, execute, flush caches. Skip any of these steps and you’re taking unnecessary risks with your database.
For the official and most up-to-date documentation, refer to the WP-CLI search-replace command reference. The WP-CLI handbook is also an excellent resource for understanding how the tool works under the hood and exploring related commands like wp db query and wp db optimize.
Database Migration Domain Migration Search Replace WordPress Database WP-CLI
Last modified: February 12, 2026