The old domain name is still in 800 posts
You rebranded last month. The domain changed, the plugin name changed, maybe even the company name changed. The content editor updated the homepage and the about page. The other 800 posts still say the old thing. Every one of them.
It isn't just branding. A partner's URL rotated, a product was discontinued, a typo slipped through a batch import and now lives in 300 WooCommerce product descriptions. Fixing them one by one is not a plan. It is a punishment.
What most people do
search-replace both touch the database directly and can corrupt serialized values. They also operate outside WordPress, so revisions are not created and there is no per-post rollback.A better way: one command, dry run first
TrueCommander's replace command runs through wp_update_post, so WordPress handles serialization, fires the right hooks, and creates a revision for every changed post. You get a per-post rollback trail without any extra effort.
-dry-run=true to commitAlways run with -dry-run=true first. The live run updates posts immediately via wp_update_post and creates revisions for rollback, but there is no cross-post undo. Preview the match count, confirm the right posts are in scope, then drop -dry-run=true to commit.
How it works
-dry-run=true The command scans every post in scope and reports how many would change. Nothing is written. Run this until the match count looks right.-dry-run=true and run again. Each matched post is updated through wp_update_post, which fires hooks and creates a revision automatically.| Parameter | Details |
|---|---|
-from | Text or regex pattern to find. Required. |
-to | Replacement text. Leave empty to delete matched text. |
-dry-run | Default false. Set to true to preview without writing any changes. |
-case_sensitive | Default true. Set to false for case-insensitive matching. |
-whole_word | Default false. Adds \b word boundaries so "cat" does not match "category". Mutually exclusive with -regex. |
-regex | Default false. Treats -from as a PCRE pattern (no delimiters needed). Mutually exclusive with -whole_word. |
-fields | CSV of fields to search: content, title, excerpt. Default content. |
-post_type | CSV of post type slugs. Default: all public types except attachments. |
-post_status | Default publish,draft,private,pending,future. |
-post_ids | Comma-separated post IDs. Overrides -post_type, -post_status, and -limit. Useful when chaining from filter posts. |
-limit | Max posts per run. Default 500, max 5000. |
| Can be used in |
Real example
You migrated a client's site from staging.example.com to example.com three weeks ago. Everything looked fine in the browser, but a spot-check of product descriptions today revealed the old staging URL in 63 posts. It is in the content field only, never in titles.
You run tp replace -from="staging.example.com" -to="example.com" -dry-run=true -post_type=product. The dry run comes back: 63 matches across 58 products. Good. You drop -dry-run=true and run again. Fifty-eight products updated, 58 revisions created. You open three at random in the editor, confirm the URL is gone, and you are done in under two minutes.
One product has a custom field you forgot about. No problem: the revision for that post is sitting in the WordPress revision screen, one click to restore.
Goes further with TrueCommander
filter posts Run filter posts to get the exact post IDs you want, then pass them to tp replace -post_ids=... so only those posts are touched.