Gutenberg blocks are a great way to add content to your WordPress website. They allow you to easily create custom layouts and add content in a visually appealing way. But what if you want to create dynamic blocks that can be updated with new content?
Dynamic blocks are blocks that can be updated with new content without having to manually edit the block each time. This is especially useful for content that changes frequently, such as news stories, blog posts, or product listings.
Creating dynamic blocks for Gutenberg is relatively easy. Here’s how to do it:
1. Create a Custom Block
The first step is to create a custom block. This can be done by using the Gutenberg Block Editor or by using a plugin like Advanced Gutenberg.
2. Add Dynamic Content
Once you have your custom block created, you can add dynamic content to it. This can be done by using the “Dynamic” block type. This block type allows you to add content from a variety of sources, such as posts, pages, custom post types, and more.
3. Configure the Block
Once you have added the dynamic content, you can configure the block to display the content in the way you want. This includes setting the number of items to display, the order of the items, and more.
4. Publish the Block
Once you have configured the block, you can publish it and it will be available on your website. The dynamic content will be updated automatically, so you don’t have to worry about manually updating it each time.
Creating dynamic blocks for Gutenberg is a great way to add content to your website in a visually appealing way. With a few simple steps, you can create dynamic blocks that can be updated with new content automatically.
Are you still puzzled by Gutenberg? Or are you among those who firmly believe in the potential of the block editor and want to find out how far can push their creativity using the block editor?
Whatever category of users you fall into, Gutenberg is here to stay and this post will give you an in-depth overview of what goes on behind the scenes of the WordPress block editor. But that’s not all!
Following our previous tutorial where we provided a general introduction to Gutenberg block development, this article goes beyond the basics, introducing more advanced block types. These blocks are referred to as dynamic blocks.
Today you’ll learn what dynamic blocks are, how they work, and all that you need to know to create dynamic blocks from scratch.
So, what are Gutenberg dynamic blocks, and what are the key differences between static and dynamic blocks?
What Are Dynamic Blocks? An Example
While with static blocks the content is manually added by the user while editing a post or page, with dynamic blocks the content is loaded and processed on the fly on page load. With dynamic blocs, the block content is picked up from the database and displayed as is or resulting from any kind of data manipulation.
Let’s explain that with an example. Say you want to create a group of nested blocks showing post author details with a selection of the latest posts from the same author.
As Gutenberg users, you could use the following blocks:
- The Heading core block
- The Post Author core block
- The Latest Posts core block
You could also create a group including those blocks and add the group to reusable blocks for future use.
It’s quite straightforward, isn’t it? You can create a dynamic block and add it to your posts and pages in a snap.
As of WordPress 5.9, the block editor provides more than 90 different blocks, and chances are that you’ll find the block that’s right for you just out of the box. And, if you’d need more, run a quick search in the WordPress plugin directory and you’ll find a lot of free plugins providing additional blocks.
But what if you’re a WordPress developer – or you are planning a career as a WordPress developer? Perhaps you have very specific needs and can’t find the block you are searching for, or you simply want to gain new professional skills. In such situations, you may want to learn how to create your dynamic blocks.
Gutenberg Dynamic Blocks from a Developer’s Perspective
Dynamic blocks have two main use cases.
The first use case is when you need to update a block’s content when the page containing the block has not been updated. For example, this happens when the block includes a list of the latest posts or comments, and in general whenever the content of the block is dynamically generated using data retrieved from the database.
The second use case is when an update to the block code needs to be immediately shown on the front end. Using a dynamic block instead of a static block causes the changes to be immediately applied to all occurrences of the block.
On the other hand, if you’d change the HTML produced by a static block, the user will see an invalidation dialog until every single instance of the previous version of the block is removed and replaced with the new version, or you mark the old version as deprecated (see also Deprecation and Block Validation, Deprecation and Migration Experience).
That being said, there are a few concepts you need to understand before you can start building dynamic blocks.
Application State and Data Stores
Gutenberg is a React SPA application, and everything in Gutenberg is a React component. Post title, headers, paragraphs, images and any block of HTML content in the editor is a React component, as well as sidebar and block toolbar controls.
In our previous article, we only used properties to store data. In this article, we’ll take it a step further by introducing the concept of state.
To put it simply, the state
object is a plain JavaScript object used to contain information about a component. The state
of the component can change over time, and any time it changes, the component re-renders.
Similarly to the state
object, properties are plain JavaScript objects used to hold information about the component. But there’s a key difference between props and state
:
props
get passed to the component (similar to function parameters) whereasstate
is managed within the component (similar to variables declared within a function).
You may think of the state as a snapshot of data taken at a given point in time that an application stores to control a component’s behavior. For example, if the block editor settings sidebar is open, a piece of information will be stored somewhere in the state
object.
When the information is shared within a single component, we call it local state. When the information is shared across components within an application, we call it Application State.
Application State is closely related to the concept of store. According to the Redux docs:
A store holds the whole state tree of your application. The only way to change the state inside it is to dispatch an action on it.
So, Redux stores an application state in a single immutable object tree (namely a store). The object tree can only be changed by creating a new object using actions and reducers.
In WordPress, stores are managed by the WordPress data module.
Modularity, Packages, and Data Stores in Gutenberg
The Gutenberg repository is built from the ground up on several reusable and independent modules that, combined together, build the editing interface. These modules are also called packages.
The official documentation lists two different types of packages:
- Production packages make up the production code that runs in the browser. There are two types of production packages in WordPress:
- Packages with stylesheets provide stylesheets to function properly.
- Packages with data stores define data stores to handle their state. Packages with data stores can be used by third-party plugins and themes to retrieve and manipulate data.
- Development packages are used in development mode. Those packages include tools for linting, testing, building, etc.
Here we are mostly interested in packages with data stores, used to retrieve and manipulate data.
The WordPress Data Store
The WordPress data module is built upon Redux and shares the three Redux core principles, although with some key differences.
The official documentation provides the following definition:
WordPress’ data module serves as a hub to manage application state for both plugins and WordPress itself, providing tools to manage data within and between distinct modules. It is designed as a modular pattern for organizing and sharing data: simple enough to satisfy the needs of a small plugin, while scalable to serve the requirements of a complex single-page application.
By default, Gutenberg registers several data stores within the application state. Each of these stores has specific name and purpose:
Through these stores, you will be able to access a whole bunch of data:
- Data related to the current post, such as post title, excerpt, categories and tags, blocks, etc.
- Data related to the user interface, i.e. if a toggle is turned on or off.
- Data related to the entire WordPress installation, such as registered taxonomies, post types, blog title, authors, etc.
These stores live in the global wp
object. To access the state of a store, you’ll use the select
function.
To see how it works, create a new post or page and launch your browser’s inspector. Find the console and type in the following line of code:
wp.data.select("core")
The result will be an object including a list of functions you can use to get data from the core
data store. These functions are called selectors and act as interfaces to access state values.
The WordPress data store includes information about WordPress in general and selectors are the way you’ll get that information. For example, getCurrentUser()
returns details for the current user:
wp.data.select("core").getCurrentUser()
Another selector you can use to retrieve user details from the data store is getUsers()
:
wp.data.select("core").getUsers()
The following image shows the response object:
To get details for a single user, you can just type the following line:
wp.data.select("core").getUsers()[0]
Using the same selector you can also retrieve site users with author
role assigned:
wp.data.select( 'core' ).getUsers({ who: 'authors' })
You can also retrieve registered taxonomies:
wp.data.select("core").getTaxonomies()
A list of the registered post types:
wp.data.select("core").getPostTypes()
Or a list of plugins:
wp.data.select("core").getPlugins()
Now let’s try to access a different data store. To do that, you’ll still use the select
function, but providing a different namespace. Let’s try the following:
wp.data.select("core/edit-post")
Now you’ll get the following response object.
If you want to know whether the settings sidebar is open or not, you would use the isEditorSidebarOpened
selector:
wp.data.select("core/edit-post").isEditorSidebarOpened()
This function returns true
if the sidebar is open:
How to Access Post Data
You should now have a basic understanding of how to access data. Now we’ll take a closer look at a specific selector, the getEntityRecords
function, which is the selector that gives access to the post data.
In the block editor, right click and select Inspect. In the Console tab, copy and paste the following line:
wp.data.select("core").getEntityRecords('postType', 'post')
This sends a request to the Rest API and returns an array of records corresponding to the last published blog posts.
getEntityRecords
accepts three parameters:
kind
string: Entity kind (i.e.postType
).name
string: Entity name (i.e.post
).query
?Object: Optional terms query (i.e.{author: 0}
).
You can build more specific requests using an object of arguments.
For example, you may decide that the response should only contain posts in a specified category:
wp.data.select("core").getEntityRecords('postType', 'post', {categories: 3})
You can also request only articles from a given author:
wp.data.select("core").getEntityRecords('postType', 'post', {author: 2})
If you click on any of the records returned by getEntityRecords
, you get a list of properties for the selected record:
If you want the response to include the featured image, you’ll need to add an additional argument to your previous request:
wp.data.select("core").getEntityRecords('postType', 'post', {author: 2, _embed: true})
Now you should have a better understanding of how to access the WordPress datastore and retrieve post details. For a closer view at the getEntityRecords
selector, see also Requesting data in Gutenberg with getEntityRecords.
How to Create a Dynamic Block: An Example Project
After our long theoretical premise, we can move on to practice and create a dynamic block using the tools we introduced in our previous block development tutorial.
In that article, we discussed:
- How to Set Up a WordPress Development Environment
- What is a Block Scaffolding
- How To Build a Static Gutenberg Block
That’s why we won’t be covering those topics in depth in the present article, but feel free to refer to our previous guide for any additional information, or just for a refresher.
Set Up A JavaScript Development Environment
Let’s start by setting up a JavaScript development environment.
Install or Update Node.js
First, install or update Node.js. Once you are done, launch your command line tool and run the following command:
node -v
You should see your node version.
Set Up Your Development Environment
Next, you’ll need a development environment for WordPress. For our examples, we used DevKinsta, our free WordPress development tool that enables you to launch a local WordPress website in no time.
But you are still free to choose any WordPress local development environment you like, such as MAMP or XAMPP, or even the official wp-env solution.
If you are using DevKinsta, click on New WordPress Site or on Custom Site, fill in the form fields and push Create site.
The installation process takes a minute or two. When it is complete, launch your local WordPress development website.
Set Up Your Block Plugin
What you need now is a starter block plugin. To avoid all the hassle of a manual configuration, the WordPress core developer team released the @wordpress/create-block tool, which is the official zero configuration tool for creating Gutenberg blocks.
We covered @wordpress/create-block
in depth in our previous article, so here we can jump start the set-up right away.
In your command line tool, navigate to the /wp-content/plugins folder:
Once there, run the following command:
npx @wordpress/create-block
You are now ready to install the @wordpress/create-block
package:
To confirm, type y
and press Enter.
This generates the plugin’s PHP, SCSS, and JS files in interactive mode.
Below are the details we will be using in our example. Feel free to change these details according to your preferences:
Once you hit enter, it downloads and configures the plugin.
The process may take a couple of minutes. When it is complete, you should see the following screen:
You will see a list of the commands you can run from within the plugin directory:
$ npm start
– Start the build for development.$ npm run build
– Build the code for production.$ npm run format
– Format files.$ npm run lint:css
– Lint CSS files.$ npm run lint:js
– Lint JavaScript files.$ npm run packages-update
– Update WordPress packages to the latest version.
Okay, now move to the plugin directory with the following command:
cd author-plugin
And start your development build:
npm start
Next, navigate to the Plugins screen in your WordPress dashboard and activate the Author box plugin:
Now you can check if the plugin is working correctly. Create a new post and start typing /
to launch the quick inserter:
You’ll also find the Author box block in the Block Inserter, under the Widgets category. Select the block to add it to the editor canvas:
You’re done. Now save the post and preview the page to check if the block displays correctly.
The Block Scaffolding
We covered the block scaffolding in our previous post. So, here we will only provide a quick overview of the files we are going to modify for our examples.
The Root Folder
The root folder is where you’ll find the main PHP file and several subfolders.
author-plugin.php
By default, the @wordpress/create-block
package provides the following PHP file:
/**
* Plugin Name: Author box
* Description: An example block for Kinsta readers
* Requires at least: 5.8
* Requires PHP: 7.0
* Version: 0.1.0
* Author: Carlo
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: author-plugin
*
* @package author-box
*/
/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/reference/functions/register_block_type/
*/
function author_box_author_plugin_block_init() {
register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'author_box_author_plugin_block_init' );
In the heading, you’ll notice the details we entered on setup.
With static blocks, most of the time you’ll be working on the JavaScript files located in the src folder. With dynamic blocks, you’ll write PHP code to display the block content on the front end.
The src Folder
The src folder is your development folder. Here you’ll find the following files:
- block.json
- index.js
- edit.js
- save.js
- editor.scss
- style.scss
block.json
The block.json is your metadata file. @wordpress/create-block
generates the following block.json file:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "author-box/author-plugin",
"version": "0.1.0",
"title": "Author box",
"category": "widgets",
"icon": "businessperson",
"description": "An example block for Kinsta readers",
"supports": {
"html": false
},
"textdomain": "author-plugin",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
For a closer view at the block.json file in general, please refer to our previous blog post.
index.js
The index.js file is where you register the block type on the client:
import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';
registerBlockType('author-box/author-plugin', {
edit: Edit,
save,
});
edit.js
The edit.js file is where you’ll build the block interface rendered in the editor:
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';
export default function Edit() {
return (
<p {...useBlockProps()}>
{__('Author box – hello from the editor!', 'author-plugin')}
</p>
);
}
save.js
The save.js file contains the script that builds the block content to be saved into the database. We won’t use this file in this tutorial:
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
export default function save() {
return (
<p {...useBlockProps.save()}>
{__('Author box – hello from the saved content!', 'author-plugin')}
</p>
);
}
Building the Block to Render in the Editor
Open your project in Visual Studio Code or any code editor you like.
If you are using Visual Studio Code, go to Terminal -> New Terminal. This will launch a terminal window on your project’s root folder.
In the terminal (or in your favorite command line tool), type in the following command:
npm start
You’re now running the node environment in development mode.
From here on, you’ll be following two different routes. To render the block in the editor, you’ll work in the edit.js file. To render the block on the front-end, you’ll need to write PHP code in the main plugin file.
Now roll up your sleeves because the coding begins:
Register the Block on the Server
First, you have to register the block on the server and write the PHP code to retrieve data from the database.
In the author-plugin.php file, you will need to pass a second argument to the register_block_type
function:
function author_box_author_plugin_block_init() {
register_block_type( __DIR__ . '/build', array(
'render_callback' => 'author_box_author_plugin_render_author_content'
) );
}
add_action( 'init', 'author_box_author_plugin_block_init' );
The second argument is an array of arguments for registering a block type (see the full list of available arguments here). In the code above we have only provided render_callback
, which determines the callback function that renders the block on the screen.
Next, you will declare the function:
function author_box_author_plugin_render_author_content() {
return 'Hello World!';
}
Save the file, create a new post or page, and add the Author Box block to the editor canvas.
The block editor is still showing the starter block, as we haven’t changed the edit.js file yet.
But if you preview the post in the front-end, you’ll see that the original block content has now been replaced by the “Hello World” string.
Now, since the HTML rendered on the front-end is generated by the PHP file, there will be no need for the save
function to return anything. So let’s go straight to the save.js file and change the code as shown below:
export default function save() {
return null;
}
Define Block Attributes
Now you need a place to store user settings. For example, the number of post items to retrieve from the database, whether to display or not a specified field, etc. To do that, you’ll define a number of attributes
in the block.json file.
For example, you could give the user the ability to determine the number of posts to be included in the block, the option to display featured image, date, excerpt, and/or hide/show the author’s profile picture.
Here is the full list of attributes we will use to build our example block:
{
...
"attributes": {
"numberOfItems": {
"type": "number",
"default": 3
},
"columns": {
"type": "number",
"default": 1
},
"displayDate": {
"type": "boolean",
"default": true
},
"displayExcerpt": {
"type": "boolean",
"default": true
},
"displayThumbnail": {
"type": "boolean",
"default": true
},
"displayAuthorInfo": {
"type": "boolean",
"default": true
},
"showAvatar": {
"type": "boolean",
"default": true
},
"avatarSize": {
"type": "number",
"default": 48
},
"showBio": {
"type": "boolean",
"default": true
}
}
}
Build the Block to Be Rendered in the Editor
The getEntityRecords
selector is included in the @wordpress/data
package. To use it, you’ll need to import the useSelect
hook from that package in your edit.js
file:
import { useSelect } from '@wordpress/data';
Next, add the following code to the Edit()
function:
const posts = useSelect( ( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': 3
});
});
In the code above, we hardcoded the number of posts. But you may want to give users the ability to set a different number of posts. You can use an attribute for that.
In your block.json you should have defined a numberOfItems
attribute. You can use it in your Edit
function as shown below:
export default function Edit( { attributes } ) {
const { numberOfItems } = attributes;
const posts = useSelect( ( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': numberOfItems
});
});
console.log( posts );
return (
...
);
}
You won’t see the posts on the screen yet, but run a console.log
and see what happens in your browser inspector’s console:
useSelect
may take two arguments: an inline callback and an array of dependencies. Both return a memoized version of the callback that only changes when one of the dependencies changes.
So, in order to refetch posts on every numberOfItems
attribute change, you have to change the Edit
function as shown below:
export default function Edit( { attributes } ) {
const { numberOfItems } = attributes;
const posts = useSelect(
( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': numberOfItems
});
},
[ numberOfItems ]
);
console.log(posts);
return (
...
);
}
Next you have to render your list of posts. To do that you could use the built-in JavaScript map
method:
export default function Edit( { attributes } ) {
const { numberOfItems } = attributes;
const posts = useSelect(
( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': numberOfItems
});
},
[ numberOfItems ]
);
console.log(posts);
return (
<div { ...useBlockProps() }>
<ul>
{ posts && posts.map( ( post ) => {
return (
<li key={ post.id }>
<h5>
<a href={ post.link }>
{
post.title.rendered ?
post.title.rendered :
__( 'Default title', 'author-plugin' )
}
</a>
</h5>
</li>
)
})}
</ul>
</div>
);
}
First, it checks if you have at least one post in the array, then runs the loop.
Note that, as we’re using the map
method with a React component, we are also using a key
attribute to assign the post ID to the current list item.
post.link
and post.title.rendered
render the post URL and title respectively.
The image below shows the full list of the post
object properties.
The code above is just a basic example of usage of getEntityRecords
. Now it’s time to put our knowledge into practice.
Say you want to prevent your block from rendering HTML tags that the user may have added to the post title. WordPress provides a RawHTML
component for that.
First, you’ll import the component from the @wordpress/element package:
import { RawHTML } from '@wordpress/element';
Next, you’ll wrap the post title within a RawHTML
element:
<div { ...useBlockProps() }>
<ul>
{ posts && posts.map((post) => {
return (
<li key={ post.id }>
<h5>
<a href={ post.link }>
{ post.title.rendered ? (
<RawHTML>
{ post.title.rendered }
</RawHTML>
) : (
__( 'Default title', 'author-plugin' )
)}
</a>
</h5>
</li>
)
})}
</ul>
</div>
And that’s it. Now add an HTML tag to your post title and save the post. Then test your code with and without RawHTML
and see how your block’s content changes on the screen.
Add the Date
WordPress provides a number of JavaScript functions to manage and format dates. To use those functions you’ll first need to import them from the @wordpress/date
package in your edit.js file:
import { dateI18n, format, __experimentalGetSettings } from '@wordpress/date';
dateI18n
: Format a date, translating it into site’s locale.format
: Format a date.__experimentalGetSettings
: Display the date in the format set in WordPress general settings.
Those functions are not documented, but you’ll find useful examples in the source code of several blocks. See for instance the latest-posts and post-date edit.js files.
Now add the displayDate
attribute:
const { numberOfItems, displayDate } = attributes;
Then add the following code within the <li>
element:
{
displayDate && (
<time
className="wp-block-author-box-author-plugin__post-date"
dateTime={ format( 'c', post.date_gmt ) }
>
{ dateI18n(
__experimentalGetSettings().formats.date,
post.date_gmt
)}
</time>
)
}
What happens here?
- If
displayDate
istrue
, then display the date using atime
element. - The
dateTime
attribute provides the time and/or date of the element in one of the allowed formats. dateI18n
retrieves the date in localized format. This function works in a way similar to the PHPPHPdate_i18n
WordPress function.
Add the Excerpt
Now it should be easy to add the post excerpt. First, take a look at the excerpt
property in the browser’s inspector. You’ll see that the actual content is stored in excerpt.rendered
.
Next, add the displayExcerpt
attribute to the attributes
object:
const { numberOfItems, displayDate, displayExcerpt } = attributes;
Then add the following code before the </li>
closing tag in the Edit
function:
{
displayExcerpt &&
post.excerpt.rendered && (
<p>
<RawHTML>
{ post.excerpt.rendered }
</RawHTML>
</p>
)
}
If you are not familiar with JavaScript, here and above we used the Short Circuit Evaluation, whereby, if all conditions are true, then the value of the last operand is returned (read more in Inline If with Logical && Operator and Logical AND (&&)).
Finally, you can test your code again. Change the attribute value in the block.json file and see what happens in the editor.
Add the Featured Image
Now you need to add the code that renders the featured images. Start adding the displayThumbnail
attribute to attributes
:
const {
numberOfItems,
displayDate,
displayExcerpt,
displayThumbnail
} = attributes;
Now you need to figure out where the featured image is stored. As we mentioned above, to get the featured image you need to add a new _embed
argument to your query. Back to your code, change the query arguments as follows:
const posts = useSelect(
( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': numberOfItems,
'_embed': true
});
},
[ numberOfItems ]
);
Here we simply added '_embed': true
to the array of arguments. This provides a post
object containing the _embedded
property, which provides the image details you need to dispay the featured images.
Now you should know where to find the image details.
You just need to add the code that renders the image on the screen:
{
displayThumbnail &&
post._embedded &&
post._embedded['wp:featuredmedia'] &&
post._embedded['wp:featuredmedia'][0] &&
<img
className="wp-block-author-box-author-plugin__post-thumbnail"
src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url }
alt={ post._embedded['wp:featuredmedia'][0].alt_text }
/>
}
Save the file, switch to the block editor, and check if the image displays correctly when the displayThumbnail
attribute is set to true
.
Add Sidebar Controls
So far we have been using the attribute default values set in the block.json. But from our previous article we know that we can define event handlers to give users the ability to assign custom values to each attribute.
To do that, you’ll add a set of controls to the block settings sidebar. In edit.js, import the following components from the corresponding packages:
import {
useBlockProps,
InspectorControls
} from '@wordpress/block-editor';
import {
PanelBody,
PanelRow,
QueryControls,
ToggleControl,
RangeControl
} from '@wordpress/components';
InspectorControls
: Contains sidebar settings that affect the entire block (see on GitHub)PanelBody
: Adds a collapsible container to the Settings Sidebar (see on GitHub)PanelRow
: Produces a generic container for sidebar controls (see on GitHub)QueryControls
: Provides settings controls to build a query (see on GitHub)ToggleControl
: Provides a toggle button for users to enable/disable a specific option (see on GitHub)RangeControl
: Is used to make selections from a range of incremental values (see on GitHub)
Next, you need to update the Edit
function to use the controls now available. First, modify the Edit
function as follows:
export default function Edit( { attributes, setAttributes } ) {
const {
numberOfItems,
columns,
displayExcerpt,
displayDate,
displayThumbnail
} = attributes;
const posts = useSelect(
( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'per_page': numberOfItems,
'_embed': true
});
},
[ numberOfItems ]
);
...
}
Note the setAttributes
property passed to the Edit
function.
Now you can add the corresponding elements to your JSX code:
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Content Settings', 'author-plugin' ) }>
<PanelRow>
<QueryControls
numberOfItems={ numberOfItems }
onNumberOfItemsChange={ ( value ) =>
setAttributes( { numberOfItems: value } )
}
minItems={ 1 }
maxItems={ 10 }
/>
</PanelRow>
<PanelRow>
<RangeControl
label={ __( 'Number of Columns', 'author-plugin' ) }
value={ columns }
onChange={ ( value ) =>
setAttributes( { columns: value } )
}
min={ 1 }
max={ 4 }
required
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Featured Image', 'author-plugin' ) }
checked={ displayThumbnail }
onChange={ () =>
setAttributes( { displayThumbnail: ! displayThumbnail } )
}
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Date', 'author-plugin' ) }
checked={ displayDate }
onChange={ () =>
setAttributes( { displayDate: ! displayDate } )
}
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Display Excerpt', 'author-plugin' ) }
checked={ displayExcerpt }
onChange={ () =>
setAttributes( { displayExcerpt: ! displayExcerpt } )
}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<div { ...useBlockProps() }>
...
</div>
</>
);
Wow, that’s a lot of code, isn’t it? But it’s pretty easy to understand.
The element attributes that are the most worthy of your attention here are onNumberOfItemsChange
in QueryControls
and onChange
in RangeControl
and ToggleControl
. Those attributes set the event handlers needed to enable the user to customize the appearance and/or behavior of a block.
You will also notice that we used <>
and </>
tags, which are the short syntax for declaring React fragments.
Now, save your file, hop over into the editor, and refresh the page:
Is everything in there? Then let’s move on and add the post author’s details.
Find the Post Author
As we mentioned above, our block will show a list of articles written by the same author as the current post.
To get the post author’s ID, you’ll import the getCurrentPostAttribute
selector from the core/editor
datastore:
wp.data.select( 'core/editor' ).getCurrentPostAttribute( 'author' )
getCurrentPostAttribute
returns an attribute value for the saved post.
Once you get the author ID, you can change the query as shown below:
const posts = useSelect(
( select ) => {
const _authorId = select( 'core/editor' ).getCurrentPostAttribute( 'author' );
return select( 'core' ).getEntityRecords( 'postType', 'post', {
'author': _authorId,
'per_page': numberOfItems,
'_embed': true
});
},
[ numberOfItems ]
);
With this code, you’ll get a list of n
articles by the same author as the current post.
Now that you have the author ID, you can also use it to fetch additional data from the database.
Display Author Details
Since we don’t have any documentation available, we used the code from the core Post Author block as a reference.
To display author details, you first need to import a new dependency:
import { forEach } from 'lodash';
Then, in the Edit
function, update the attributes
object as follows:
const {
numberOfItems,
columns,
displayExcerpt,
displayDate,
displayThumbnail,
displayAuthorInfo,
showAvatar,
avatarSize,
showBio
} = attributes;
Once done, you’ll edit the code seen in the previous section to retrieve author details:
const { authorDetails, posts } = useSelect(
( select ) => {
const _authorId = select( 'core/editor' ).getCurrentPostAttribute( 'author' );
const authorDetails = _authorId ? select( 'core' ).getUser( _authorId ) : null;
const posts = select( 'core' ).getEntityRecords( 'postType', 'post', {
'author': _authorId,
'per_page': numberOfItems,
'_embed': true
});
return {
authorDetails: authorDetails,
posts: posts
};
},
[ numberOfItems ]
);
Note that we used the getUser
selector to get the author details.
Next, you may want to get the author’s avatar. The code below builds an array of items storing avatar URLs and sizes:
const avatarSizes = [];
if ( authorDetails ) {
forEach( authorDetails.avatar_urls, ( url, size ) => {
avatarSizes.push( {
value: size,
label: `${ size } x ${ size }`,
} );
} );
}
Then you’ll add the sidebar panels and controls to enable users to customize the author’s area in the block:
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Author Info', 'author-plugin' ) }>
<PanelRow>
<ToggleControl
label={ __( 'Display Author Info', 'author-plugin' ) }
checked={ displayAuthorInfo }
onChange={ () =>
setAttributes( { displayAuthorInfo: ! displayAuthorInfo } )
}
/>
</PanelRow>
{ displayAuthorInfo && (
<>
<PanelRow>
<ToggleControl
label={ __( 'Show avatar' ) }
checked={ showAvatar }
onChange={ () =>
setAttributes( { showAvatar: ! showAvatar } )
}
/>
{ showAvatar && (
<SelectControl
label={ __( 'Avatar size' ) }
value={ avatarSize }
options={ avatarSizes }
onChange={ ( size ) => {
setAttributes( {
avatarSize: Number( size ),
} );
} }
/>
) }
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Bio', 'author-plugin' ) }
checked={ showBio }
onChange={ () =>
setAttributes( { showBio: ! showBio } )
}
/>
</PanelRow>
</>
) }
</PanelBody>
...
</InspectorControls>
...
</>
);
The image below shows the updated settings sidebar:
Finally, you can add the author’s section to your block:
return (
<>
<InspectorControls>
...
</InspectorControls>
<div { ...useBlockProps() }>
{ displayAuthorInfo && authorDetails && (
<div className="wp-block-author-box-author-plugin__author">
{ showAvatar && (
<div className="wp-block-author-box-author-plugin__avatar">
<img
width={ avatarSize }
src={
authorDetails.avatar_urls[
avatarSize
]
}
alt={ authorDetails.name }
/>
</div>
) }
<div className="wp-block-author-box-author-plugin__author-content">
<p className="wp-block-author-box-author-plugin__name">
{ authorDetails.name }
</p>
{ showBio &&
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
authorDetails?.description &&
authorDetails.description.length > 0 && (
<p className="wp-block-author-box-author-plugin__description">{ authorDetails.description }</p>
) }
</div>
</div>
)}
<ul>
...
</ul>
</div>
</>
);
The following image shows how it renders on the screen.
Now save your edit.js file and run your tests. Your block should include different elements depending on block settings.
One last thing is still missing: the number of columns to display articles.
Change the Number of Columns
To give the user the ability to show article previews in columns, we defined the columns
attribute in the block.json file. We also included a columns
attribute in the script and created a settings control to allow users to change the number of columns, although this change has no effect at the moment.
In the JSX code above you should have noticed that we added CSS classes to several elements:
Classes assigned to elements in the Author section:
wp-block-author-box-author-plugin__author
wp-block-author-box-author-plugin__avatar
wp-block-author-box-author-plugin__author-content
wp-block-author-box-author-plugin__name
wp-block-author-box-author-plugin__description
Classes assigned to elements in the content section:
wp-block-author-box-author-plugin__post-items
wp-block-author-box-author-plugin__post-thumbnail
wp-block-author-box-author-plugin__post-title
wp-block-author-box-author-plugin__post-date
wp-block-author-box-author-plugin__post-excerpt
One class is still missing. The name of this class will be generated dynamically to reflect the number of columns set by the user.
Go back to the Edit.js
file and modify the ul
element as follows:
<ul className={ `wp-block-author-box-author-plugin__post-items columns-${ columns }` }>
...
</ul>
We added a new columns-${ columns }
class according to the Template literals syntax to insert an expression inside a string. This way, the attribute attached to the ul
element will depend on user settings (e.g. columns-1
, columns-2
, etc.).
Now open the style.scss
file and replace the existing code with the following:
.wp-block-author-box-author-plugin {
background-color: #21759b;
color: #fff;
padding: .6em;
ul.wp-block-author-box-author-plugin__post-items {
padding: 0;
list-style-type: none;
display: grid;
gap: .5em;
@for $i from 2 through 4 {
&.columns-#{ $i } {
grid-template-columns: repeat(#{ $i }, 1fr);
}
}
li {
list-style: none;
img.wp-block-author-box-author-plugin__post-thumbnail {
height: auto;
max-width: 100%;
}
}
}
}
.wp-block-author-box-author-plugin__author {
display: flex;
flex-wrap: wrap;
}
.wp-block-author-box-author-plugin__avatar {
margin-right: 1em;
}
.wp-block-author-box-author-plugin__author-content {
flex-basis: 0;
flex-grow: 1;
}
We won’t go deep in that code, being beyond the scope of this article. But if you wish to dive deeper, you could refer to the following resources:
And that’s it for the rendering of the block in the editor.
Building the Block to Render on the Page
Now that the code that renders the block in the editor is complete, we can move on and build the block for rendering on the front end.
As we mentioned earlier, when it comes to dynamic blocks, the plugin file is responsible to generate the HTML to be rendered on the front end.
So, open the main file of your plugin (author-plugin.php in our example).
The first thing to do is to make the block attributes available to the WordPress PHP function. In your PHP file, change the function definition as follows:
function author_box_author_plugin_render_author_content( $attr ) {
...
}
Now you can use the WordPress functions to retrieve and manipulate data. For example, you can use get_posts
to retrieve the latest blog posts (read more in our in-depth article covering the get_posts
function):
function author_box_author_plugin_render_author_content( $attr ) {
$args = array(
'numberposts' => $attr['numberOfItems'],
);
$my_posts = get_posts( $args );
if( ! empty( $my_posts ) ){
$output="<ul>";
foreach ( $my_posts as $p ){
$output .= '<li><a href="' . esc_url( get_permalink( $p->ID ) ) . '">'
. $p->post_title . '</a></li>';
}
$output .= '</ul>';
}
return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}
The function above retrieves the latest numberOfItems
blog posts from your WordPress database (by default post_type
is set to post
) and returns an array of $post
objects. Than it iterates over the array to build the list items.
If you inspect the HTML output, you’ll note that it’s a simple list of posts, like the one shown in the following image:
In our previous article we mentioned that you’ll use the useBlockProps
React hook to mark the block’s wrapper element in your JSX code. You’ll need to do the same in your PHP function.
WordPress provides the get_block_wrapper_attributes
function for that.
So, change your PHP code as follows:
function author_box_author_plugin_render_author_content( $attr ) {
$args = array(
'numberposts' => $attr['numberOfItems']
);
$my_posts = get_posts( $args );
if( ! empty( $my_posts ) ){
$output="<div " . get_block_wrapper_attributes() . '>';
$output .= '<ul>';
foreach ( $my_posts as $p ){
$title = $p->post_title ? $p->post_title : 'Default title';
$url = esc_url( get_permalink( $p->ID ) );
$output .= '<li>';
$output .= '<a href="' . $url . '">' . $title . '</a>';
$output .= '</li>';
}
$output .= '</ul>';
$output .= '</div>';
}
return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}
Now a wp-block-author-box-author-plugin
class has been assigned to the container element and the block has a different background color.
Then the get_posts
function gets WP_Posts
data and the foreach
cycle builds the list items (see also How to Display get_posts Returned Data).
Add Featured Image, Date, and Excerpt
Next, you’ll need to add post thumbnails, dates and excerpts. In the same file, change your PHP code as following:
function author_box_author_plugin_render_author_content( $attr ) {
$args = array(
'numberposts' => $attr['numberOfItems']
);
$my_posts = get_posts( $args );
if( ! empty( $my_posts ) ){
$output="<div " . get_block_wrapper_attributes() . '>';
$output .= '<ul class="wp-block-author-box-author-plugin__post-items columns-">';
foreach ( $my_posts as $p ){
$title = $p->post_title ? $p->post_title : 'Default title';
$url = esc_url( get_permalink( $p->ID ) );
$thumbnail = has_post_thumbnail( $p->ID ) ? get_the_post_thumbnail( $p->ID, 'medium' ) : '';
$output .= '<li>';
if( ! empty( $thumbnail ) && $attr['displayThumbnail'] ){
$output .= $thumbnail;
}
$output .= '<h5><a href="' . $url . '">' . $title . '</a></h5>';
if( $attr['displayDate'] ){
$output .= '<time datetime="' . esc_attr( get_the_date( 'c', $p ) ) . '">' . esc_html( get_the_date( '', $p ) ) . '</time>';
}
if( get_the_excerpt( $p ) && $attr['displayExcerpt'] ){
$output .= '<p>' . get_the_excerpt( $p ) . '</p>';
}
$output .= '</li>';
}
$output .= '</ul>';
$output .= '</div>';
}
return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}
The foreach
loop iterates over the $my_posts
array. At each iteration, several conditions check attribute values and build the output accordingly.
Now take a look at the output on the screen:
Now you can run your tests. Change date, excerpt, and thumbnail settings and check how the block content changes on the front-end.
Display Posts in Columns
In our JavaScript code, we used a columns-${ columns }
class to display post previews in columns. Now we need to do the same in PHP.
To do that, you simply have to add these two lines of code:
$num_cols = $attr['columns'] > 1 ? strval( $attr['columns'] ) : '1';
$output .= '<ul class="wp-block-author-box-author-plugin__post-items columns-' . $num_cols . '">';
This will append a columns-n
class to the ul
element containing the post previews. Now the number of columns displayed on the page should match the number of columns set in the block settings.
Build the Author Box
Last, you need to build the box containing the author’s details, including avatar, name, and description.
Within the callback function, you’ll need to add a set of conditions to check the current value of each attribute:
if( $attr['displayAuthorInfo'] ){
$output .= '<div class="wp-block-author-box-author-plugin__author">';
if( $attr['showAvatar'] ){
$output .= '<div class="wp-block-author-box-author-plugin__avatar">'
. get_avatar( get_the_author_meta( 'ID' ), $attr['avatarSize'] )
. '</div>';
}
$output .= '<div class="wp-block-author-box-author-plugin__author-content">';
$output .= '<div class="wp-block-author-box-author-plugin__name">'
. get_the_author_meta( 'display_name' )
. '</div>';
if( $attr['showBio'] ){
$output .= '<div class="wp-block-author-box-author-plugin__description">'
. get_the_author_meta( 'description' )
. '</div>';
}
$output .= '</div>';
$output .= '</div>';
}
The code is quite straightforward. It checks the current value of each attribute, and if it is true
, then it generates the necessary HTML.
Now save your PHP file and compare the block in the editor vs the same block on the front end.
You’ll find the full code of the example block in this public Gist.
Recommended Resources for Dynamic Block Development
If you perked up your ears while reading this article and started recognizing the professional development opportunities coming with learning how to create Gutenberg blocks, well, our advice is to continue exploring and acquiring new skills in the technologies behind block development.
Although reliable official documentation is still missing, nonetheless there are excellent resources out there, both free and paid, we consulted while writing this article. Among the many resources available, we recommend the following:
Official Resources
Recommended Tutorials from WordPress Core Contributors
JavaScript, React, and Redux Resources
Related Resources from Kinsta
Summary
We have reached the end of this (second) long journey through Gutenberg block development.
In this article, we covered some advanced topics, such as Application State and Redux stores. But hopefully, you should now have a better understanding of block development in general.
Yes, Node.js, Webpack, Babel, React, and Redux skills are essential when it comes to building advanced Gutenberg blocks, but you don’t need to be a React ninja to get started. Learning how to develop Gutenberg blocks doesn’t necessarily have to be complicated. Just do it with the right motivation and by following the appropriate learning path.
And we hope this article – and the previous one – provide you with the right map to find your path and get started with Gutenberg development right away.
Up to you now! Have you created dynamic blocks yet? Do you have any examples to share with us? And what were the biggest hurdles in your experience? Feel free to drop a comment below.