Making the Most of WordPress Block Transforms
Moving a site into the WordPress block editor eventually means dealing with legacy content. Old posts often arrive as a single Classic block — everything wrapped in one large chunk of HTML. You can convert that to proper blocks with a click of “Convert to Blocks,” and the editor does a surprisingly good job at guessing what each piece should become. But for sites with a lot of custom markup, the default conversion can only do so much.
Why Raw Transforms Matter
Block transforms are the editor’s mechanism for moving content between block types. Most native blocks support from and to transformations — a paragraph can become a quote, a list, or a heading. Those are useful, but they work on blocks that already exist. The more interesting case is when the editor needs to build blocks from raw HTML, which is exactly what happens during that “Convert to Blocks” action, and also when you paste content into the editor.
That raw transform process is where the editor scans the HTML and decides which blocks to create. Native blocks cover the common cases well. Problems arise when your content structure doesn’t fit the expected patterns, or when you want specific bits of HTML turned into a custom block with particular attributes set.
Registering a From-Raw Transform
Consider a site that stores code inside markup like <pre><code> with additional data in attributes. A default conversion might turn that into the native code block, but you might want it in your own custom block instead, complete with settings mapped from those attributes. That requires registering a transform on your custom block.
A transform from raw code has an isMatch function and a transform function. The isMatch check runs against every node in the HTML being parsed; return true when the structure is one you want to handle:
registerBlockType("my/code-block", {
title: __("My Code Block"),
// ...
transforms: {
from: [
{
type: "block",
priority: 7,
// ...
},
{
type: "raw",
priority: 8,
isMatch: (node) =>
node.nodeName === "PRE" &&
node.children.length === 1 &&
node.firstChild.nodeName === "CODE",
transform: function (node) {
let pre = node;
let code = node.querySelector("code");
let codeType = "html";
if (pre.classList.contains("language-css")) {
codeType = "css";
}
if (pre.getAttribute("rel") === "CSS") {
codeType = "css";
}
if (pre.classList.contains("language-javascript")) {
codeType = "javascript";
}
if (code.classList.contains("language-javascript")) {
codeType = "javascript";
}
// ... other data wrangling...
return createBlock("csstricks/code-block", {
content: code.innerHTML,
codeType: codeType,
});
},
},
],
to: [
// ...
],
// ...
}
In that example, the transform matches HTML that looks like <pre ...><code ...>. When it matches, the transform runs and passes extracted data and content into a createBlock call. The JavaScript in the transform gives you full control over what content goes into the new block and how attributes map over.
Registering transform rules for both directions — to and from the relevant built-in blocks — keeps the editor experience consistent. For a simple block that only stores text content, that’s mostly about moving the content around. More complex blocks need to pass additional data, but the principle is the same.
Transforms for Paste Events
Raw transforms apply just as much when pasting external content into the editor as they do in a full post conversion. This is why pasting a table from another page or a YouTube link frequently produces the right block type automatically.
These paste-time transforms are a good place to intercept URLs you want to handle specially. If you have a custom embed block for a particular service — say, CodePen — you can register a from transform that checks pasted content for a matching URL:
{
type: "raw",
priority: 8, // higher number to beat out default
isMatch: (node) =>
node.nodeName === "P" &&
node.innerText.startsWith("https://codepen.io/"),
transform: function (node) {
return createBlock("cp/codepen-gutenberg-embed-block", {
penURL: node.innerText,
penID: getPenID(node.innerText), // helper function
});
},
}
When a codepen.io URL shows up, that transform intercepts it and creates the custom block instead of falling back to the generic embed handler.
Practical Notes and Open Questions
Raw transforms are a bit messy. The isMatch approach is broad — it runs on every node — so it is important to be very specific about what you match. The upside is that this is the only real path for converting a site with lots of custom HTML and shortcodes into something the block editor can manage well.
Documentation on block transforms is relatively thin, though the official developer docs cover the basics of registering transforms with a block. One area still being figured out is whether it is possible to run these transforms in bulk across all existing database content, not just on a per-post basis when a user triggers a conversion or a paste. That remains an open question for site operators looking to automate the conversion of an entire archive.
Adopting the block editor is not a temporary detour. It has become a core part of WordPress, and the editor is expected to expand into more areas of the platform over time. Learning how to bend transforms to your benefit — especially for unruly legacy content — makes that transition significantly less painful.



