Sign inDocsBlogAboutSupportContact
Content

Bulk find-and-replace across every post without phpMyAdmin

Search content, titles, and excerpts across your entire site in one command. Preview matches first with a dry run, then commit. No database client, no plugin, no manual rows.

5 min read May 2026 replace

Interface examples use sample data. Results, counts, and timing vary by site and configuration.

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

Run a raw SQL UPDATE in phpMyAdmin Works once. Then you remember serialized data, encoding differences, and the fact that there is no preview step. One typo in the WHERE clause and you have updated the wrong column on every row.
Install a search-replace plugin Better-Search-Replace and WP CLI's 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.
Edit posts manually in the block editor Fine for five posts. Painful for fifty. Impossible for five hundred. And you will miss at least one, because ctrl+F does not search the whole site.

How to find and replace in WordPress with SQL

The no-plugin route is a raw SQL UPDATE with the REPLACE() function, run from phpMyAdmin or Adminer. Take a full database backup first, this runs outside WordPress and has no undo:

-- back up the database first, there is no undo
UPDATE wp_posts
SET    post_content = REPLACE( post_content, 'acme-widgets.com', 'acme.com' )
WHERE  post_content LIKE '%acme-widgets.com%';

It works for a one-off on a plain-text column, but the caveats are exactly why people get burned:

  • Serialized data breaks. Widgets, page-builder layouts, and many meta values store the byte length of each string. A REPLACE() that changes a string's length leaves the stored length wrong and corrupts the whole blob. Only touch plain text fields like post_content and post_title.
  • No hooks, no revisions, no cache clear. Because it bypasses WordPress, no revision is created, so there is no per-post rollback, and object/page caches keep serving the old text until purged.
  • The table prefix may not be wp_. Check $table_prefix in wp-config.php before running, or you will update nothing (or the wrong table).

If you would rather preview first, keep revisions, and never think about serialization, the next section does the same job safely.

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.

TrueCommander
Dry run: 47 matches found
No posts were changed
47 posts would be updatedpreview only
Field content · case-sensitive
Apply with -dry-run=false -confirm=REPLACErevisions kept

Shown in advanced mode, where commands start with tp. In easy mode you type the same command without the tp prefix.

Preview first with -dry-run=true. Apply only with -dry-run=false -confirm=REPLACE. Live runs scan at most 500 candidates and update at most 100 matching posts per batch. A successful live run returns a one-hour actor-bound recovery token that restores exact original fields independently of revisions.

How it works

1
Preview with -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.
2
Commit the live run Apply with -dry-run=false -confirm=REPLACE. At most 500 candidates are scanned and 100 matching posts updated per batch. Preserve the one-hour recovery token and inspect the receipt.
3
Roll back individual posts if needed Because revisions are created, you can restore any single post from the WordPress revision screen. There is no all-at-once undo, so the dry run in step 1 matters.
ParameterDetails
-fromText (or regex pattern when -regex=true) to find.
-toReplacement text. Leave empty to delete the matched text.
-dry-runDefaults to true and reports matches without touching posts. A live run must set false and provide -confirm=REPLACE.
-case_sensitiveDefault true. Set false to match regardless of case.
-whole_wordOnly match standalone words (adds \b boundaries). Mutually exclusive with regex.
-regexTreat -from as a PCRE pattern (no delimiters. pattern is wrapped in ~...~u internally). Mutually exclusive with whole_word.
-fieldsComma-separated: content, title, excerpt. Default: content.
-post_typeExact comma-separated public editor post type slug(s). Default: supported public UI types excluding attachments. Unknown or unsupported values fail closed.
-post_statusExact comma-separated safe status(es): publish, draft, private, pending, future. Unknown values fail closed.
-post_idsRestrict to at most 500 positive whole-number IDs. Type, status, field, authority, scan, and update bounds still apply.
-limitCandidate posts scanned in this batch: whole number from 1 to 500. Default 500. At most 100 matching posts are updated.
Can be used in
-confirmExact value REPLACE required together with -dry-run=false.
-restartStart this exact actor/mode/scope again at the first eligible ID. Defaults false; use deliberately because replacements may be reapplied.
-restoreRestore exact original fields using an unexpired one-hour token from a live run. Cannot be combined with replacement parameters.

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.

For example, preview the old hostname in eligible product content. Confirm the scope and matches, then apply using -dry-run=false -confirm=REPLACE. Inspect the receipt, continue any remaining batch, and preserve the short-lived recovery token.

Replacement targets supported post fields rather than arbitrary custom metadata. Use the unexpired recovery token with tp replace -restore=TOKEN if you need to restore original fields. Keep a site backup for broader recovery.

Goes further with TrueCommander

Frequently asked questions

Ready?

Replace that text across every post.

One of 100 command variants. Part of the TrueCommander package.

Cookies. The short version.

Essential cookies keep the cart and theme working. Analytics only fire if you say yes. Read our policy.