Use search-replace in WP-CLI
The WP-CLI search-replace
command can be used to replace strings in a selection of database tables. This is useful for updating post content and options, especially when changing URLs for sites on a WordPress multisite.
Note
There is a known bug in WP-CLI that makes certain database commands not work as expected. This guide clarifies the workarounds needed in different environments.
--dry-run
Always run a search-replace
command first with the --dry-run
option. This will output the potential results of the command without actually running it, making it possible verify the results prior to making changes in the database.
Adding the --dry-run
option to the end of the command will make it easier to remove it when running the final command.
--report-changed-only
By including the --report-changed-only
option in a search-replace
command, only the fields and tables affected by the command will be reported. This keeps the reporting output shorter which can be more readable in some cases. The --report-changed-only
option is useful and compatible to run with the --dry-run
option.
Single-site install, entire site
Add --all-tables
or --all-tables-with-prefix
to a command:
wp search-replace oldstring newstring --all-tables --dry-run
The difference between flags
--all-tables-with-prefix
: Enable replacement on any tables that match the table prefix (even if not registered on$wpdb
).--all-tables
: Enable replacement on all tables in the database, regardless of the prefix or--url
flag. This overrides--network
,--url
and--all-tables-with-prefix
.
The differences between these two flags primarily apply to sites on a WordPress multisite.
The --all-tables
and --all-tables-with-prefix
flags will be ignored if a table is specified in the command (e.g., wp_comments
, wp_commentmeta
).
Single-install and targeting specific table(s)
Same as above, but with table name(s):
wp search-replace oldstring newstring wp_comments --all-tables --dry-run wp search-replace oldstring newstring wp_comments wp_commentmeta --all-tables --dry-run
Multisite, all sites
wp search-replace oldstring newstring --all-tables --dry-run
Multisite, only root site (ID 1)
This one is a bit trickier since we need to use --all-tables
or --all-tables-with-prefix
, and site ID 1 tables are not prefixed with wp_1_
. We can’t target the main site very easily and instead, have to manually specify the tables to hit.
Just ID 1 tables:
wp search-replace oldstring newstring wp_a8c_cron_control_jobs wp_commentmeta wp_comments wp_links wp_options wp_postmeta wp_posts wp_term_taxonomy wp_termmeta wp_terms --all-tables --dry-run
Also the network tables (be sure to pick and choose as/if needed):
wp search-replace oldstring newstring wp_a8c_cron_control_jobs wp_blog_versions wp_blogmeta wp_blogs wp_commentmeta wp_comments wp_links wp_options wp_postmeta wp_posts wp_registration_log wp_signups wp_site wp_sitemeta wp_term_taxonomy wp_termmeta wp_terms wp_usermeta wp_users --all-tables --dry-run
Multisite, child site (not ID 1)
Use --all-tables-with-prefix
without a --url
flag:
wp search-replace oldstring newstring --all-tables-with-prefix wp_3_* --dry-run
Note: The wildcard table selection (e.g. wp_3_*
) is what is needed to restrict, if a specific site is not targeted with --url
(or else the prefix ends up being just wp_
because it’s the main site and ends up being affecting the entire database).
Specify a site on a multisite with --url
instead of site prefix:
wp search-replace oldstring newstring --all-tables-with-prefix --url=domain.go-vip.net --dry-run
If specifying --url
, the --all-tables-with-prefix
option is still required. Remember, --all-tables-with-prefix
doesn’t take an argument, it determines automatically from context, which is determined by the --url
.
This will run on all tables that belong to the specific sub-site. Once this is run, the last remaining instance of the oldstring
from the wp_blogs
table must also be replaced. See the next section for the exact command.
Multisite, specific table(s)
Updating a network table (--url
will be ignored due to --all-tables
):
wp search-replace oldstring newstring wp_blogs --all-tables --dry-run
If the --all-tables-with-prefix
option is included for a search-replace
on a subsite (using --url
), this will not work because it will be restricted to wp_<id>_*
prefix.
Using a wildcard to get all comments
tables:
wp search-replace oldstring newstring wp_*comments --all-tables --dry-run
The --network
flag will not be necessary; just use --all-tables*
instead. The --network
option does not even work without the --all-tables*
flag.
Beware of overlapping URLs or partial strings
Before proceeding with running the commands on multiple blogs inside the multisite, make sure there are no overlapping URLs which would cause some issues during the search replace on shared tables. For instance, if the multisite is a directory multisite, the blogs must be processed with directory in the URL first.
For example, on a multisite that has a root site (Site ID 1: oldexample.com) and a subsite (Site ID 2: oldexample.com/blog2), doing wp search-replace oldexample.com newexample.com wp_blogs
will not only override the URL for blog ID 1, but also for Site ID 2, since the oldexample.com
is a part of its URL.
It would be better to do the search-replace first for Site ID 2:
wp search-replace "oldexample.com/blog2" "newexample.com/blog2" --all-tables --dry-run
Then Site ID 1:
wp search-replace "oldexample.com" "newexample.com" --all-tables --dry-run
Similarly, if two directory multisite’s blogs have overlapping slugs, eg: /ham
and /hamburger
, the /hamburger
blog must be processed first, as replacing /ham
first would also replaced the /hamburger
due to the overlap on the beginning of the slug.
Finally, if changing example.com
to www.example.com
, this string would also change existing instances of www.example.com
to www.www.example.com
, and emails to @www.example.com
. Instead, target the search at //example.com
Elementor
If the site’s theme is Elementor, then some data may be stored in a serialized fashion that won’t be hit by WP-CLI’s search-replace command. Elementor provides its own search-replace utility that is effective for updating that data.
The utility is found in a site’s WordPress admin at: /wp-admin/admin.php?page=elementor-tools#tab-replace_url
For more information, review Elementor’s guide to “Search and Replace URLs using Elementor“.
Note that Elementor’s search-replace accepts only full URLs, so searching for multiple variations (http
vs https
, www
vs non-www
, et al) is required.
Other plugin-related issues
Plugins that store serialized content in JSON format can also have unexpected search-replace
issues.
A search-replace for the JSON encoded URLs needs to account for escape slashes ( \/
) included in the values:
wp search-replace "https:\/\/old.site.com\/subfolder\/" "https:\/\/new.site.com\/" --all-tables --report-changed-only --dry-run
Clearing the cache
Before reviewing the results of a successful search-replace
command, the cache must be flushed:
wp cache flush
On multisite, the --network
or --url
options may need to be included depending on where updates were made.
For example, to flush the cache for all sites on a multisite:
wp cache flush --network
To flush the cache for one specific site on a multisite:
wp cache flush --url=http://example.com
If a command produces unintended results, a database backup can be downloaded and imported to restore a database to a previous state.