A client came to me last month, a busy magazine site, moving off a development subdomain that had an emoji in the URL. Don’t ask why. They were finally heading for a clean ASCII domain, and their content was a graveyard of Gutenberg blocks, JSON escapes and Punycode.
My first instinct, like any dev who has done this a hundred times, was to reach for the blunt instrument: wp search-replace. Swap the domains in the database, call it a day. That was the mistake. With the modern block editor, a plain string replacement is surgery with a sledgehammer, and it breaks things you did not know were in there.
Why the WordPress Importer URL migration beats SQL queries
WordPress does not store plain URLs anymore. They sit inside JSON strings in block comments, or encoded as HTML entities, or buried in CSS inside a style attribute. My search-replace missed half of them and corrupted the rest. What I had left was background images that would not load and cover blocks that looked like they had been through a shredder.
This is where the recent work on the WordPress Importer comes in. If you have not looked at that plugin in a few years, and plenty of us have not, it is worth opening again. The WordPress Importer URL migration features arrived in version 0.9.5, and they change how this job is done.
The importer now reads the context of the data it moves. Instead of seeing a string of text, it uses the new structured data parsers in WordPress Core to work out which strings are really URLs and which only look like one. Punycode, Unicode escapes in JSON, and those %-encodings tangled up with HTML entities are all handled.
<?php
/**
* Example of how the internal logic handles complex block markup
* during the migration process.
*/
class bbioon_Url_Migration_Handler {
public function bbioon_process_block_data( $content ) {
// The new importer uses BlockMarkupUrlProcessor under the hood.
// It catches things that a simple REPLACE() query misses:
// 1. JSON-escaped URLs: https:\/\/yummy-\uD83C\uDF7E-recipes.org
// 2. HTML Entity encoded URLs: https//...
// 3. CSS-encoded URLs in style attributes.
return $content;
}
}
The parsers doing the work
BlockMarkupUrlProcessor does the coordinating, calling into specialized parsers such as WP_HTML_Processor and CSSProcessor. It knows that a CSS class name which happens to look like a domain should be left alone, while the background image URL in that same block’s style attribute has to change. That distinction is what was missing before, and it takes hours of manual cleanup off the job.
There is more to this than saving a few minutes. On a site with hundreds of thousands of rows, repeated UPDATE queries against post_content can lock your busiest tables for hours. Because the rewriting happens during the import itself, the old URLs never reach the new database at all. There was a good breakdown of this on make.wordpress.org recently, covering how WordPress/php-toolkit is turning “data liberation” into something you can use.
What this means for your next migration
Stop treating WordPress content as flat text. The block editor made the stored markup a lot more complicated, and the migration tools are only now catching up. A few things worth remembering on your next move:
- Tick the “Migrate URLs” box in WordPress Importer 0.9.5 and later.
- Do not lean on a SQL search-replace alone for a block-based site.
- Test Punycode and emoji domains properly before you go live.
Migrations are stressful whatever you do. Using tools that understand how the content is actually stored is the difference between a quiet launch and a weekend spent debugging someone else’s mess. If you are tired of chasing broken links and want the site to work, drop my team a line. We have probably seen your exact problem before.