"Block" is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage. The idea combines concepts of what in WordPress today we achieve with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience.
For more context, refer to What Are Little Blocks Made Of? from the Make WordPress Design blog.
The following documentation outlines steps you as a developer will need to follow to add your own custom blocks to WordPress's editor interfaces.
If you're not already accustomed to working with JavaScript in your WordPress plugins, you may first want to reference the guide on Including CSS & JavaScript in the Theme Handbook.
At a minimum, you will need to enqueue scripts for your block as part of a
enqueue_block_editor_assets
action callback, with a dependency on the
wp-blocks
and wp-element
script handles:
<?php
// myplugin.php
function myplugin_enqueue_block_editor_assets() {
wp_enqueue_script(
'myplugin-block',
plugins_url( 'block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element' )
);
}
add_action( 'enqueue_block_editor_assets', 'myplugin_enqueue_block_editor_assets' );
The enqueue_block_editor_assets
hook is only run in the Gutenberg editor
context when the editor is ready to receive additional scripts and stylesheets.
There is also an enqueue_block_assets
hook which is run under both the
editor and front-end contexts. This should be used to enqueue stylesheets
common to the front-end and the editor. (The rules can be overridden in the
editor-specific stylesheet if necessary.)
The following sections will describe what you'll need to include in block.js
to describe the behavior of your custom block.
Note that all JavaScript code samples in this document are enclosed in a
function that is evaluated immediately afterwards. We recommend using either
ES6 modules
as used in this project
(documentation on setting up a plugin with Webpack + ES6 modules coming soon)
or these
"immediately-invoked function expressions"
as used in this document. Both of these methods ensure that your plugin's
variables will not pollute the global window
object, which could cause
incompatibilities with WordPress core or with other plugins.
Let's imagine you wanted to define a block to show a randomly generated image in a post's content using lorempixel.com. The service provides a choice of category and you'd like to offer this as an option when editing the post.
Take a step back and consider the ideal workflow for adding a new random image:
- Insert the block. It should be shown in some empty state, with an option to choose a category in a select dropdown.
- Upon confirming my selection, a preview of the image should be shown next to the dropdown.
At this point, you might realize that while you'd want some controls to be shown when editing content, the markup included in the published post might not appear the same (your visitors should not see a dropdown field when reading your content).
This leads to the first requirement of describing a block:
You will need to provide implementations both for what's to be shown in an editor and what's to be saved with the published content.
To eliminate redundant effort here, share common behaviors by splitting your code up into components.
Now that we've considered user interaction, you should think about the underlying values that determine the markup generated by your block. In our example, the output is affected only when the category changes. Put another way: the output of a block is a function of its attributes.
The category, a simple string, is the only thing we require to be able to generate the image we want to include in the published content. We call these underlying values of a block instance its attributes.
With these concepts in mind, let's explore an implementation of our random image block:
<?php
// random-image.php
function random_image_enqueue_block_editor_assets() {
wp_enqueue_script(
'random-image-block',
plugins_url( 'block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element' )
);
}
add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_assets' );
// block.js
( function( blocks, element ) {
var el = element.createElement,
source = blocks.source;
function RandomImage( props ) {
var src = 'http://lorempixel.com/400/200/' + props.category;
return el( 'img', {
src: src,
alt: props.category
} );
}
blocks.registerBlockType( 'myplugin/random-image', {
title: 'Random Image',
icon: 'format-image',
category: 'media',
attributes: {
category: {
type: 'string',
source: source.attr( 'img', 'alt' )
}
},
edit: function( props ) {
var category = props.attributes.category,
children;
function setCategory( event ) {
var selected = event.target.querySelector( 'option:checked' );
props.setAttributes( { category: selected.value } );
event.preventDefault();
}
children = [];
if ( category ) {
children.push( RandomImage( { category: category } ) );
}
children.push(
el( 'select', { value: category, onChange: setCategory },
el( 'option', null, '- Select -' ),
el( 'option', { value: 'sports' }, 'Sports' ),
el( 'option', { value: 'animals' }, 'Animals' ),
el( 'option', { value: 'nature' }, 'Nature' )
)
);
return el( 'form', { onSubmit: setCategory }, children );
},
save: function( props ) {
return RandomImage( { category: props.attributes.category } );
}
} );
} )(
window.wp.blocks,
window.wp.element
);
Let's briefly review a few items you might observe in the implementation:
- When registering a new block, you must prefix its name with a namespace for your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name.
- You will use
createElement
to describe the structure of your block's markup. See the Element documentation for more information. - Extracting
RandomImage
to a separate function allows us to reuse it in both the editor-specific interface and the published content. - The
edit
function should handle any case where an attribute is unset, as in the case of the block being newly inserted. - We only change the attributes of a block by calling the
setAttributes
helper. Never assign a value on the attributes object directly. - React provides conveniences for working with
select
element withvalue
andonChange
props.
By concerning yourself only with describing the markup of a block given its attributes, you need not worry about maintaining the state of the page, or how your block interacts in the context of the surrounding editor.
But how does the markup become an object of attributes? We need a pattern for
encoding the values into the published post's markup, and then retrieving them
the next time the post is edited. This is the motivation for the block's
attributes
property. The shape of this object matches that of the attributes
object we'd like to receive, where each value is a
source
which tries to find the desired value from the markup of the block.
In the random image block above, we've given the alt
attribute of the image a
secondary responsibility of tracking the selected category. There are a few
other ways we could have achieved this, but the category value happens to work
well as an alt
descriptor. In the attributes
property, we define an object
with a key of category
whose value tries to find this alt
attribute of the
markup. If it's successful, the category's value in our edit
and save
functions will be assigned. In the case of a new block or invalid markup, this
value would be undefined
, so we adjust our return value accordingly.
Registers a new block provided a unique name and an object defining its behavior. Once registered, the block is made available as an option to any editor interface where blocks are implemented.
title: string
- A human-readable localized label for the block. Shown in the block picker.icon: string | WPElement | Function
- Slug of the Dashicon to be shown in the control's button, or an element (or function returning an element) if you choose to render your own SVG.attributes: Object | Function
- An object of attribute schemas, where the keys of the object define the shape of attributes, and each value an object schema describing thetype
,default
(optional), andsource
(optional) of the attribute. Ifsource
is omitted, the attribute is serialized into the block's comment delimiters. Alternatively, defineattributes
as a function which returns the attributes object.category: string
- Slug of the block's category. The category is used to organize the blocks in the block inserter.edit( { attributes: Object, setAttributes: Function } ): WPElement
- Returns an element describing the markup of a block to be shown in the editor. A block can update its own state in response to events using thesetAttributes
function, passing an object of properties to be applied as a partial update.save( { attributes: Object } ): WPElement | String
- Returns an element describing the markup of a block to be saved in the published content. This function is called before save and when switching to an editor's HTML view.keywords
- An optional array of keywords used to filter the block list.
Returns type definitions associated with a registered block.
Returns settings associated with a registered control.
Because many blocks share the same complex behaviors, the following components
are made available to simplify implementations of your block's edit
function.
When returned by your block's edit
implementation, renders a toolbar of icon
buttons. This is useful for block-level modifications to be made available when
a block is selected. For example, if your block supports alignment, you may
want to display alignment options in the selected block's toolbar.
Because the toolbar should only be shown when the block is selected, it is
important that a BlockControls
element is only returned when the block's
focus
prop is
truthy,
meaning that focus is currently within the block.
Example:
( function( blocks, element ) {
var el = element.createElement,
BlockControls = blocks.BlockControls,
AlignmentToolbar = blocks.AlignmentToolbar;
function edit( props ) {
return [
// Controls: (only visible when focused)
props.focus && (
el( BlockControls, { key: 'controls' },
el( AlignmentToolbar, {
value: props.align,
onChange: function( nextAlign ) {
props.setAttributes( { align: nextAlign } )
}
} )
)
),
// Block content: (with alignment as attribute)
el( 'p', { key: 'text', style: { textAlign: props.align } },
'Hello World!'
),
];
}
} )(
window.wp.blocks,
window.wp.element
);
Note in this example that we render AlignmentToolbar
as a child of the
BlockControls
element. This is another pre-configured component you can use
to simplify block text alignment.
Alternatively, you can create your own toolbar controls by passing an array of
controls
as a prop to the BlockControls
component. Each control should be
an object with the following properties:
icon: string
- Slug of the Dashicon to be shown in the control's toolbar buttontitle: string
- A human-readable localized text to be shown as the tooltip label of the control's buttonsubscript: ?string
- Optional text to be shown adjacent the button icon as subscript (for example, heading levels)isActive: ?boolean
- Whether the control should be considered active / selected. Defaults tofalse
.
To create divisions between sets of controls within the same BlockControls
element, passing controls
instead as a nested array (array of arrays of
objects). A divider will be shown between each set of controls.
Render a rich
contenteditable
input,
providing users the option to add emphasis to content or links to content. It
behaves similarly to a
controlled component,
except that onChange
is triggered less frequently than would be expected from
a traditional input
field, usually when the user exits the field.
The following properties (non-exhaustive list) are made available:
value: string
- Markup value of the editable field. Only valid markup is allowed, as determined byinline
value and available controls.onChange: Function
- Callback handler when the value of the field changes, passing the new value as its only argument.placeholder: string
- A text hint to be shown to the user when the field value is empty, similar to theinput
andtextarea
attribute of the same name.multiline: String
- A tag name to use for the tag that should be inserted when Enter is pressed. For example:li
in a list block, andp
for a block that can contain multiple paragraphs. The default is that only inline elements are allowed to be used in inserted into the text, effectively disabling the behavior of the "Enter" key.
Example:
( function( blocks, element ) {
var el = element.createElement,
Editable = blocks.Editable;
function edit( props ) {
function onChange( value ) {
props.setAttributes( { text: value } );
}
return el( Editable, {
value: props.attributes.text,
onChange: onChange
} );
}
// blocks.registerBlockType( ..., { edit: edit, ... } );
} )(
window.wp.blocks,
window.wp.element
);