What are Block Transforms and Why You Should Use Them

Chain transforming into birds
Block Transforms and Your Block

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.

Transforming an Image to a Cover Block
Transforming an Image to a Cover Block

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.

Block Attributes in Block.json
Block Attributes in Block.json

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.

List Parent Block with Child List Item Inner Blocks
List Parent Block with Child List Item innerBlocks

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

Crowd Transforming into an Arrow
Automated Transforms Can Be Seamless

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.

From Blocks Transform
From Blocks Transform

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.

To a Block Transformation
To a Block Transformation

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

Transform progression from a piece of paper to an origami bird.
There are Multiple 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.

registerBlockType Code Initialization Example
registerBlockType Code Initialization Example

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:

  1. The block name or path to block.json file
  2. 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:

Attributes in register_block_type
Attributes in register_block_type

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)
Getting Attributes in the Block Editor Using Console
Getting Attributes in the Block Editor Using Console

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.

Button Block Block.json Example
Button Block Block.json Example

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.jsonCode 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.

Kadence Blocks in the src/blocks Folder
Kadence Blocks in the src/blocks Folder

You can then drill down and view the block.json file.

Kadence Blocks Accordion Block.json File
Kadence Blocks Accordion Block.json File

Intermission

Beautiful sunset with birds in a heart shape.

Transforming examples

So far here is what we’ve learned:

  1. Transforms go “from” something or “to” something
  2. There are several transform types
  3. 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.

Easily share 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.

Better Click to Tweet Block to Quotes DLX
Better Click to Tweet Block to Quotes DLX

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:

Enter Text Transform to QuotesDLX
Enter Text Transform to QuotesDLX

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.

Taking Raw Input and Transforming it to a Block
Taking Raw Input and Transforming it to a Block

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.

  1. Transforms in the Block Editor Handbook
  2. Block Transforms in 10up Gutenberg Best Practices
  3. 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.

Ronald Huereca
By: Ronald Huereca
Published On: on April 17, 2023

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.

Leave Your Valuable Feedback

Your email address will not be published. Required fields are marked *

Shopping Cart
  • Your cart is empty.
Scroll to Top