Technote

Development workflow Practical

Series WP-CLI automation recipes Part 3 of 8

Safe search-replace: serialisation, guid and dry runs

Replace a domain with raw SQL and widget and theme settings quietly go blank. Serialised data stores string lengths — and switching tools removes the problem entirely.

If you cloned staging from production, moved a domain, or switched from http to https, the old address is scattered through the database: image paths in post content, widget settings, theme options, plugin configuration.

At which point a single UPDATE … REPLACE() suggests itself. That one line corrupts data. Understand why, and the temptation never returns.

Why a raw SQL replace breaks things

WordPress stores array and object settings as a serialised PHP string in a single column. That format records the length of every string alongside the string itself.

a:1:{s:4:"logo";s:35:"https://staging.example.kr/logo.png";}
                        ↑ declares the following string as 35 characters

after a raw SQL replace (the length is untouched)
a:1:{s:4:"logo";s:35:"https://example.kr/logo.png";}
                        ↑ actually 27 — the declaration now lies

unserialize() on that value fails, and when it fails get_option() hands back false. On screen, widget settings, customiser values and custom fields look as though they were reset. There is no error anywhere. This is the worst kind of failure: silent, with cause and symptom far apart.

Same replacement — only a tool that understands serialisation is safe

wp search-replace reads each value, unserialises it, replaces inside, and writes it back re-serialised, so the lengths are recomputed for you. It follows nested structures all the way down.

Dry run first

Before writing anything, look at what changes and how much of it.

wp search-replace 'https://staging.example.kr' 'https://example.kr' --dry-run --report-changed-only

You get a per-table count. Far more than expected means the search term is too short — searching for example without the domain is the classic version of this. Far fewer than expected usually means protocols are mixed, so check http:// and https://, and the www and non-www forms, separately.

Once the numbers make sense, take the backup: wp db export before.sql. Whether that file exists decides how large any accident turns out to be.

guid is an identifier, not a link

The form you actually run carries one more flag.

wp search-replace 'https://staging.example.kr' 'https://example.kr' --skip-columns=guid --report-changed-only

guid looks like a URL but is a permanent identifier rather than an address. Front-end links are generated from your permalink structure; the guid is what feed readers use to decide whether they have seen an item before.

So changing it makes every old post reappear as new in subscribers’ feed readers. Your site looks perfect and the damage happens entirely on other people’s screens. WordPress documents this value as one never to change after creation, and making the flag a habit is the cheapest way to honour that.

How far to reach

By default only tables WordPress itself registers are touched. Plugin tables are excluded even when they share the prefix, so widen the scope deliberately when you need to.

Replacement scope — wider going down, and harder to undo

Three more flags are worth knowing. --precise forces the PHP path instead of the fast one, trading speed for certainty; --export=out.sql writes a replaced dump to a file without touching the database, which is ideal during a migration; and on multisite you want --network together with --url.

Checking afterwards

Do not walk away when the command finishes. Check three things: that wp option get home and siteurl report the new address; that widget and customiser screens still hold their values, which is your proof that serialisation survived; and that images actually load.

If the browser console still warns about mixed content, an http:// path is left somewhere — run one more replacement for the protocol alone.

The full step-by-step for migrations like this is published on our process page, and related articles live in the development workflow archive.

Next part

When commands alone will not do it, you write a script. The next part covers eval-file — and the wall everybody hits within the first five minutes: passing arguments.

More on this topic

All technotes

Development workflow Practical

Turning taste arguments into rule checks

"It feels a bit cramped" can be neither argued with nor fixed. Spacing off the scale, colour off the palette, contrast below threshold, missing states — four rules…

Designers 9 min read

Development workflow Practical

Do not swap everything at once

A full swap makes every problem appear at the same moment — which means none of them can be attributed. So you switch one template at a time.

Designers 6 min read

Development workflow Practical

Adding an SCSS build, and whether to commit the output

WordPress themes are expected to deploy without a build step, which leads to the opposite conclusion from ordinary application code — and to its own costs.

Developers 7 min read

₩270,000 · Join the program