Gutenberg’s block transforms system allows one block to be converted into another without losing content. This is what lets you right-click a Paragraph block and choose “Transform to → Heading”, or select multiple Image blocks and merge them into a Gallery. Transforms are defined in a block’s JavaScript registration object under the transforms property as two arrays: from defines how other entities — other block types, raw HTML, shortcodes, or file drops — become this block; to defines what this block can be transformed into. Supporting transforms is a quality-of-life feature that makes custom blocks feel like first-class Gutenberg citizens. An editor who pasted a Paragraph and then realizes it should be a custom Quote Card block should be able to transform without re-entering content, and should be able to transform it back without losing text.
Problem: Your custom my-plugin/callout block has a text attribute, but editors can't convert an existing Paragraph into a Callout, or convert a Callout back to a Paragraph — they have to delete and re-create the block instead.
Solution: Add a transforms property to the block registration with from and to entries mapping between core/paragraph and your block using the block transform type.
import { registerBlockType } from '@wordpress/blocks';
registerBlockType( 'my-plugin/callout', {
title: 'Callout',
attributes: {
text: { type: 'string', default: '' },
style: { type: 'string', default: 'info' }, // 'info' | 'warning' | 'success'
},
transforms: {
from: [
{
// Convert core/paragraph → my-plugin/callout
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'my-plugin/callout', {
text: content, // paragraph content becomes callout text
style: 'info', // default style
} );
},
},
{
// Convert multiple paragraphs into one callout (merged content)
type: 'block',
blocks: [ 'core/paragraph' ],
isMultiBlock: true,
transform: ( paragraphs ) => {
const combined = paragraphs.map( p => p.content ).join( '<br>' );
return createBlock( 'my-plugin/callout', { text: combined } );
},
},
],
to: [
{
// Convert my-plugin/callout → core/paragraph
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { text } ) => {
return createBlock( 'core/paragraph', { content: text } );
},
},
{
// Convert my-plugin/callout → core/heading
type: 'block',
blocks: [ 'core/heading' ],
transform: ( { text } ) => {
return createBlock( 'core/heading', { content: text, level: 3 } );
},
},
],
},
edit: ( { attributes, setAttributes } ) => { /* ... */ },
save: ( { attributes } ) => { /* ... */ },
} );
NOTE: The createBlock function must be imported from @wordpress/blocks. The transform callback for from.block receives the source block's attributes and must return a new block object (created with createBlock) — it does not return raw HTML. For the to direction, the callback receives your block's attributes and must return the target block object. You can also define shortcode transforms (type: 'shortcode'), enter transforms triggered by a keyboard prefix (type: 'enter'), and raw HTML transforms (type: 'raw') for migrating classic content.