Block Transforms allow you to change from one block to another. For example, you can quickly switch between an image block and a cover block with just a few clicks.
Transforms aid the user and help them switch between different block types with ease.
From a block author perspective, coding in transforms allows a lot as far as data portability. Say, for example, you have two similar blocks. Rather than having the user insert a new block, you can provide a transform to this second block and pass along the appropriate attributes.
In order to understand transforms, let’s dive into some foundational concepts.
Transforms concepts explained
Let’s go over a few concepts we’ll need to master in order to perform any transformations.
Block Attributes
Attributes are the data the block stores as users interact with your block.
For example, if a button block has a background color, that background color is stored as an attribute.
Attributes are generally defined in the block.json file for a block. Each attribute has a name and value. The value of the attribute can be changed by changing block settings or other user input.
innerBlocks
Inner blocks are any internal blocks that are part of a parent block. For example, a list block might have child “list item” blocks that are innerBlocks of the parent.
A quote block might have multiple innerBlocks to make up the quote, using paragraph blocks, for example.
The types of transform directions: from and to
Transforms allow a user to switch “from” another block to yours or “to” another block from yours.
Switching from another block to yours (from)
Let’s say you’re creating a button block for the block editor. By creating a transform, you can allow users to switch from the regular button block to your new button block.
In another case, say you have a competing block in the plugin space. You can provide a smart switch using another block’s settings by allowing a user to transform between the competing block and yours.
Here are the benefits of allowing others to transform from another block to yours:
- A better onboarding experience: Users will have less to configure when switching.
- Curated switching: You can preconfigure your block with the older block’s attributes.
- Discoverability: Users are better able to discover your block by switching from others.
If your block is close to the functionality of another, particularly core blocks, providing a “from” transformation makes sense.
Switching to another block from yours (to)
Let’s take a group of gallery blocks, for example. One block is a masonry format, while another block is a grid format.
With a block transformation from one block to another, I can switch between the masonry block and grid block with just a few clicks. This saves me the time of inserting the new block, re-adding the images, and doing some basic configuration.
Another example could be a custom headline block. By providing a way back to a core block, you are helping a user save some steps.
Here are the benefits of allowing a transform to other blocks:
- Being a good citizen: Allowing a way back to core blocks will save a lot of frustration and steps.
- Switching between related blocks: Provide a user with a quick way of trying out different variations of similar blocks.
Additional transformation types
In addition to being able to switch from block to block, there are several other transform types worth mentioning.
- block – transform from block to block.
- enter – a keyword followed by an enter press.
- files – transform when someone inserts a file in the block editor.
- prefix – a prefix followed by a space.
- raw – transform based on content added to the block editor or is transformed from classic to the block editor.
- shortcode – transform based on a shortcode.
As you can see, in addition to blocks, you’re able to transform several other ways. I’ll go over some examples in a bit.
Where to do the transforms
You would perform the transforms where you register your block in JavaScript. This would be where you use registerBlockType
to register the block.
Adding a transform example
Let’s start by showing how you would convert a paragraph block to a quotes block using transforms.
transforms: {
from: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( attributes ) => {
const { content } = attributes;
const newInnerBlocks = rawHandler( { HTML: content } );
return createBlock( 'core/quote', attributes, newInnerBlocks );
},
},
],
},
Code language: JavaScript (javascript)
There’s a lot going on here, but I’ve added a transforms
parameter in my block’s registration file. Let’s break it down.
transforms: {
from: [
/**
* From Rules here.
*/
],
to: [
/**
* To Rules here.
*/
],
},
Code language: JavaScript (javascript)
There are two sides for the transforms: the “from” side and the “to” side. In my particular case, I want to go “from” a paragraph block to a quotes block. As a result, I would use the from
parameter.
transforms: {
from: [
{
/* from transformation objects */
},
],
},
Code language: JavaScript (javascript)
I can now begin filling in my transform of the paragraph block to the quotes block.
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( attributes ) => {
const { content } = attributes;
const newInnerBlocks = rawHandler( { HTML: content } );
return createBlock( 'core/quote', attributes, newInnerBlocks );
},
},
Code language: JavaScript (javascript)
The type
parameter contains the transform type. In this case, we’re doing type block
.
Up next, I pass in an array of blocks I’d like to transform from. Since I only want to transform paragraphs, I pass an array with the core/paragraph
key.
Let’s move on to the transform.
transform: ( attributes ) => {
const { content } = attributes;
const newInnerBlocks = rawHandler( { HTML: content } );
return createBlock( 'core/quote', attributes, newInnerBlocks );
},
Code language: JavaScript (javascript)
When a block transform is initiated, the transform
property passes two values: an array of block attributes, and a collection of any innerBlocks. Since a paragraph doesn’t have innerBlocks, I just care about the attributes.
The paragraph block has an attribute named content
, which I extract from the passed attributes.
const newInnerBlocks = rawHandler( { HTML: content } );
Code language: JavaScript (javascript)
I’m using rawHandler to convert the paragraph content into innerBlocks, since this is what the quote block expects.
The rawHandler
function is a dependency of @wordpress/blocks
. Here’s an example import:
import { registerBlockType, createBlock, rawHandler } from '@wordpress/blocks';
Code language: JavaScript (javascript)
Finally, I use createBlock
to create the quotes block and pass it along any data and innerBlocks.
return createBlock( 'core/quote', attributes, newInnerBlocks );
Code language: JavaScript (javascript)
Here is the full code for this example:
import { registerBlockType, createBlock, rawHandler } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import metadata from './block.json';
import ShoelaceLogo from '../components/icons/ShoelaceLogo';
import Edit from './edit';
registerBlockType( metadata, {
edit: Edit,
save() {
return <InnerBlocks.Content />;
},
icon: <ShoelaceLogo />,
transforms: {
from: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( attributes ) => {
const { content } = attributes;
const newInnerBlocks = rawHandler( { HTML: content } );
return createBlock( 'core/quote', attributes, newInnerBlocks );
},
},
],
},
} );
Code language: JavaScript (javascript)
In the above example, I used the content
attribute of a paragraph to populate the quote. When converting to other blocks, you have to know the other block’s attributes. There are several ways to find these, which I’ll go over in the next section.
Finding the attributes
In order to transform to and from blocks, you’ll need to have some knowledge of the block’s attributes and how they work to form the block.
Here are some ways to find the attributes for the block you are wanting to add a transformation for.
In the plugin’s PHP files with register_block_type
Some older block plugins register their attributes via the function register_block_type
.
Function register_block_type
takes in two arguments:
- The block name or path to block.json file
- An array of arguments
This 2nd argument can take in array keys, one of which can be labeled attributes
. Here’s a quick example of how the code would look:
Using terminal and JavaScript commands
Select the block in the block editor, open your console tools in the browser, and type in:
wp.data.select( 'core/block-editor' ).getSelectedBlock().attributes;
Code language: JavaScript (javascript)
In the block’s block.json file
For most blocks, the best way to find the attributes is in the block’s block.json
file. In WordPress, this would involve looking at the source code for the blocks and finding the block.json
files.
For a button block, for example, I can browse the attributes rather easily using the button’s block.json file.
If you are trying to find a block.json
file in another plugin, here are some tips:
Search for block.json in the plugin’s build folder
A lot of the newer plugin’s use @wordpress/scripts
to build their block plugins. This copies a block’s block.json
files into the build folder, which is often deployed with the plugin. You can investigate this folder and see if you can find the plugin’s block.json
file in order to view the attributes.
For example, here’s how my AlertsDLX block.json files turned out in the build
folder.
.
└── alerts-dlx/
└── build/
└── js/
└── blocks/
├── bootstrap/
│ └── block.json
├── chakraui/
│ └── block.json
├── material/
│ └── block.json
└── shoelace/
└── block.json
Code language: AsciiDoc (asciidoc)
Find the plugin’s GitHub and search for their blocks
Most of the plugins out there have some kind of public version control repository of their plugin. If you find a plugin’s GitHub, chances are the block-related items will be in a src/
or src/blocks
folder.
For example, here is Kadence Blocks on GitHub.
You can then drill down and view the block.json
file.
Intermission
Transforming examples
So far here is what we’ve learned:
- Transforms go “from” something or “to” something
- There are several transform types
- Knowing the block’s attributes and finding them are needed for the transforms
Let’s go over a few real examples of some transforms that I have done throughout my plugins.
Better Click to Tweet to QuotesDLX
Better Click to Tweet is a neat little plugin and is actually the inspiration behind QuotesDLX. It allows you to add a quote to a block and on the frontend users can share the content on Twitter.
With the popularity of Better Click to Tweet, I needed a way to transform from their block over to mine. Let’s go through the transform.
transforms: {
from: [
{
type: 'block',
blocks: [ 'bctt/clicktotweet' ],
transform: ( { tweet, username, via, url, urlcustom, prompt } ) => {
/* Transform code here */
},
},
]
},
Code language: JavaScript (javascript)
I’m passing the type
as a block, and I’ve added the Better Click to Tweet block’s namespace bctt/clicktotweet
to what blocks I need to transform from.
In the transform, I’m extracting out the attributes that I need, which I retrieved from the GitHub’s block registration file.
I then read in Better Click to Tweet’s attributes, map them to the QuotesDLX block attributes, and create the QuotesDLX block.
transform: ( { tweet, username, via, url, urlcustom, prompt } ) => {
let twitterUsername = username;
if ( ! via ) {
twitterUsername = '';
}
// Map QuotesDLX attributes to Better Click to Tweet attributes.
const bcttAttributes = {
share_button_text: prompt,
twitter_username: twitterUsername,
enable_links_in_tweet: url,
has_anchor: url,
transforming: true,
};
// Convert tweet string to inner blocks.
const shareTextInnerBlocks = rawHandler( { HTML: tweet } );
if ( url && '' !== urlcustom ) {
bcttAttributes.permalink = urlcustom;
bcttAttributes.url_shortening_service = 'manual';
}
return createBlock( 'mediaron/quotes-dlx', bcttAttributes, shareTextInnerBlocks );
},
Code language: JavaScript (javascript)
Please note the use of rawHandler
, which I used to convert the Better Click to Tweet’s content variable (tweet
) to paragraph innerBlocks.
Here’s the full code for how it all looks:
transforms: {
from: [
{
type: 'block',
blocks: [ 'bctt/clicktotweet' ],
transform: ( { tweet, username, via, url, urlcustom, prompt } ) => {
let twitterUsername = username;
if ( ! via ) {
twitterUsername = '';
}
// Map QuotesDLX attributes to Better Click to Tweet attributes.
const bcttAttributes = {
share_button_text: prompt,
twitter_username: twitterUsername,
enable_links_in_tweet: url,
has_anchor: url,
transforming: true,
};
// Convert tweet string to inner blocks.
const shareTextInnerBlocks = rawHandler( { HTML: tweet } );
if ( url && '' !== urlcustom ) {
bcttAttributes.permalink = urlcustom;
bcttAttributes.url_shortening_service = 'manual';
}
return createBlock( 'mediaron/quotes-dlx', bcttAttributes, shareTextInnerBlocks );
},
},
]
},
Code language: JavaScript (javascript)
Here’s how the transform behaves in the block editor.
Adding a typing shortcut to QuotesDLX
This one is a funner transform. Just type in a phrase, hit enter, and a block shows up. Here’s an example I did with QuotesDLX.
transforms: {
from: [
{
type: 'enter',
regExp: /^quotesdlx$/,
transform: () => createBlock( 'mediaron/quotes-dlx' ),
},
],
}
Code language: JavaScript (javascript)
Instead of using block
for the type
parameter, I’m using enter
. This means when someone hits the enter key after a match is found, I can transform that input into a new block. You can find more details on the enter
type in the transforms documentation.
For this case, I’m just looking for if someone types in quotesdlx
and then presses enter.
Here’s a demo of the result:
Transforming raw input
In the last example I used a non-block type named enter
. This time I’m going to use the raw
type in order to translate a URL plugin slug into a block. I’ll show you the result first and then explain the transform.
Here’s the snippet on how I accomplished the transform.
transforms: {
from: [
{
type: 'raw',
isMatch: ( node ) => {
if ( node.nodeName === 'P' ) {
// RegEx for detecting plugin slug in WordPress URL.
const regex = /https:\/\/wordpress\.org\/plugins\/([a-z0-9-]+)\/?/i;
const match = regex.exec( node.textContent );
if ( match ) {
return true;
}
}
return false;
},
priority: 5,
transform: ( node ) => {
// Extract slug from URL.
const regex = /https:\/\/wordpress\.org\/plugins\/([a-z0-9-]+)\/?/i;
const match = regex.exec( node.textContent );
const slugMatch = match[ 1 ];
return createBlock( 'wp-plugin-info-card/wp-plugin-info-card', {
slug: slugMatch,
type: 'plugin',
layout: 'large',
sheme: 'scheme11',
loading: false,
defaultsApplied: true,
} );
},
},
],
},
Code language: JavaScript (javascript)
Let’s break this one down in order to explain it.
type: 'raw',
Code language: JavaScript (javascript)
For raw
, you can expect some text being pasted into the editor. It’s now up to me to determine if it’s a match.
isMatch: ( node ) => {
if ( node.nodeName === 'P' ) {
// RegEx for detecting plugin slug in WordPress URL.
const regex = /https:\/\/wordpress\.org\/plugins\/([a-z0-9-]+)\/?/i;
const match = regex.exec( node.textContent );
if ( match ) {
return true;
}
}
return false;
},
Code language: JavaScript (javascript)
The isMatch
portion allows me to specify what is a match or not. In this case, I’m doing a RegEx search for the WordPress plugins directory.
For example, let’s take this string: https://wordpress.org/plugins/alerts-dlx/
If someone pastes that into the block editor, it will typically convert to a WordPress embed. But if Plugin Info Card is activated, I want to use my block instead.
If it’s a match, I return true.
priority: 5,
Code language: JavaScript (javascript)
The priority allows me to load before the WordPress core embed, which has a priority of ten.
transform: ( node ) => {
// Extract slug from URL.
const regex = /https:\/\/wordpress\.org\/plugins\/([a-z0-9-]+)\/?/i;
const match = regex.exec( node.textContent );
const slugMatch = match[ 1 ];
return createBlock( 'wp-plugin-info-card/wp-plugin-info-card', {
slug: slugMatch,
type: 'plugin',
layout: 'large',
sheme: 'scheme11',
loading: false,
defaultsApplied: true,
} );
},
Code language: JavaScript (javascript)
Now it’s time to transform. I run another RegEx, verify the match again, and then create the WP Plugin Info Card block with several attributes.
Going “to” another block
So far I’ve demonstrated several “from” examples. Let’s look at a “to” one. Remember, in this case, we’re going from our block “to” another one.
to: [
{
type: 'block',
blocks: [ 'mediaron/alerts-dlx-material' ],
transform: ( attributes, innerBlocks ) => {
attributes.alertType = 'success';
attributes.variant = 'default';
attributes.className = 'is-style-success';
attributes.alertGroup = 'material';
return createBlock( 'mediaron/alerts-dlx-material', attributes, innerBlocks );
},
},
]
Code language: JavaScript (javascript)
I’m passing block
again for the type
parameter. I’ll be going from my block to the block mediaron/alerts-dlx-material
.
The transform
callback will give us the new block’s attributes and innerBlocks. We can then add or modify the attributes before creating the new block.
The innerBlocks are being passed as-is and forwarded on. Finally, the block is created with the updated attributes and innerBlocks.
Further reading
You’ve made it this far, so thank you so much for reading. Here are a few additional resources for transforms.
- Transforms in the Block Editor Handbook
- Block Transforms in 10up Gutenberg Best Practices
- An overview of innerBlocks
Conclusion
In this tutorial, I demonstrated how transforms work, how they go “to” and “from” something, and laid out several examples.
Transforms are a fun concept for me, and I love seeing how blocks make use of them to save users some headaches.
If you have any questions or additions, please leave a comment below and I’ll respond promptly.
Like this tutorial? There's more like it. Subscribe today!
Ronald Huereca founded DLX Plugins in 2022 with the goal of providing deluxe plugins available for download. Find out more about DLX Plugins, check out some tutorials, and check out our plugins.