Dynamic Blocks Explained

I published post series about creating Gutenberg blocks long time ago but somehow I missed the part about dynamic blocks. But it is quite an important topic you need to know if you want to go further and learn about Interactivity API for example and other cool stuff.

In this tutorial first of all I am going to show you the difference between static and dynamic blocks and then we will create a custom dynamic block step by step.

If you’re a beginner and don’t have any experience of creating Gutenberg blocks already, nothing to worry about, I am going to explain every part in details.

How Dynamic Blocks Work? What is their Difference from Static Blocks?

If you already know the difference between static and dynamic blocks, you can jump straight to the second part of this tutorial. But if you do not, please read with attention.

Let’s take a look at default WordPress blocks

“Paragraph” block:

It is a static block for sure, when you edit the content (or settings) of this block, both content and settings are going to be saved into the database and displayed on the website the same way (almost) they’re stored in the database.

“Latest Posts” block:

Probably you’re already starting to get to the point. And the point is when you edit a post (or a page or a template) in Gutenberg and you use the “Latest Posts” block, you might have one set of posts, but maybe tomorrow or the next week the latest posts are going to be different on your website! But our block is intended to display the actual latest posts, not the ones which were latest at the moment of editing the content in Gutenberg. So we can not just save the links to the posts statically into the database! Of course, if it is ok for you, you can just go ahead and use static “Paragraph” and “List” blocks instead.

Difference when scaffolding blocks with @wordpress/create-block package

You can also learn a lot about dynamic Gutenberg blocks when you try to scaffold two blocks – static and dynamic at the same time using @wordpress/create-block package.

How to scaffold a dynamic Gutenberg block using @wordpress/create-block

No matter which block type you’re about to choose, a block with the same functionality is going to be created on your website. It will just display a text line:

Example of static and dynamic Gutenberg scaffolded block
Both static and dynamic scaffolded blocks look exactly the same. Ok, the text is different, that’s all.

Yes, the blocks look the same and do the same thing but the whole implementation is completely different! First one is static, the second one is dynamic. Ok, but what is the difference anyway?

The only difference is – the save() method of registerBlockType() function. The dynamic block just doesn’t have it!

Instead the dynamic block has a render.php file which does the thing which in static blocks is done by the save() method. This file is also specified in the block.json:

	...
	"render": "file:./render.php"
}

The last but not least, let’s take a look at both save.js and render.php files.

save.js:

export default function save() {
	return (
		<p { ...useBlockProps.save() }>
			{ 'Example Static – hello from the saved content!' }
		</p>
	);
}

render.php:

<p <?php echo get_block_wrapper_attributes() ?>>
	<?php esc_html_e( 'Example Dynamic – hello from a dynamic block!', 'example-dynamic' ); ?>
</p>

You might notice a funny thing here – the dynamic block has a localization but the static one – doesn’t 😁

We can recap this whole chapter by saying that dynamic blocks use PHP to generate the dynamic content for the website front-end part. Some dynamic blocks can also use the ServerSideRender component to generate the editor part of the block in PHP as well, more about it below, but I don’t recommend to do that in your blocks.

Building your First Dynamic Block

I think it would be better to focus on understanding the concepts of dynamic blocks instead of building a really useful and practical block in our example.

So, let’s build a “Latest Post” block which is going to display a latest blog post dynamically (obviously). It is going to be kind of simplified version of WordPress standard “Latest Posts” block which allows to do exactly the same with the proper configuration.

Example of a custom dynamic block in Gutenberg

When I create my custom blocks I usually use @wordpress/scripts, for some of you guys the @wordpress/create-block approach may seem easier but for me it is more convenient to create from scratch rather than editing an existing block.

Below is the file structure of my dynamic block:

Gutenberg dynamic block file structure

And now let’s uncover it step by step.

block.json

In the block.json file I would like to highlight two moments:

  1. We use "render": "file:./render.php" to specify a path to a dynamic PHP file which renders the block content instead of save() method.
  2. "supports" : { "html" : false } – Definitely we don’t need to allow editors to make changes in block HTML.

Here it the file:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 2,
  "name": "rudr/latest-post",
  "version": "1.0.0",
  "title": "Latest Post",
  "category": "text",
  "icon": "admin-post",
  "description": "Dynamic block example.",
  "supports": { "html": false },
  "editorScript": "file:./build/index.js",
  "render": "file:./render.php"
}

The main plugin file:

<?php
/*
 * Plugin Name:  Misha Latest Post Block
 */
add_action( 'init', 'misha_latest_post_block' );
function misha_latest_post_block() {
	register_block_type( __DIR__ );
	// if block.json is in "/build" directory:
	// register_block_type( __DIR__ . '/build' );
}

You can also find a lot of tutorials out there where authors are trying to add render_callback into the register_block_type() function. This way is usually used in outdated Gutenberg blocks – the ones without block.json file, but not always, more about this method below.

registerBlockType()

Below is the src/index.js file:

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import metadata from './../block.json';

registerBlockType( metadata.name, {
	edit: Edit,
} );

Since our edit() method is not so complicated we can easily include it in this file as well, but I decided to move it into edit.js for convenience.

edit.js

An important moment to keep in mind here is that some developers are starting to have real issues when it comes to coding some dynamic stuff in React, that’s why they stick to a fallback solution – the ServerSideRender component. I also included it into this tutorial for completeness but please try to avoid using it.

For example using useSelect together with getEntityRecords() is not as difficult as it looks.

import { useBlockProps } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';

export default function Edit() {

  const posts = useSelect( ( select ) => {
    return select( 'core' ).getEntityRecords( 'postType', 'post' );
  }, [] );

  return (
    <div { ...useBlockProps() }>
      { ! posts && 'Loading' }
      { posts && 0 === posts.length && 'No Posts' }
      { posts && posts.length > 0 && (
        <a href={ posts[ 0 ].link }>
          { posts[ 0 ].title.rendered }
        </a>
      ) }
    </div>
  );

}

render.php

And here we go, now we can feel free to use PHP and WordPress functions, for example I am using get_posts() here.

<div <?php echo get_block_wrapper_attributes() ?>>
	<?php
		$latest_posts = get_posts( array(
			'posts_per_page' => 1,
			'post_status' => 'publish',
			'fields' => 'ids',
		) );

		if( ! $latest_posts ) {
			echo 'No posts';
		}

		$latest_post_id = reset( $latest_posts );

		printf(
			'<a href="%1$s">%2$s</a>',
			esc_url( get_permalink( $latest_post_id ) ),
			esc_html( get_the_title( $latest_post_id ) )
		);
	?>
</div>

If your block is going to have attributes and you need to access attribute values in your render.php file, you can use $attributes global variable. For example let’s say that you have an attribute which allows to change how many posts are going to be displayed at once, then you will need to make the following changes in your render.php file:

$latest_posts = get_posts( array(
	'posts_per_page' => $attributes[ 'postsToShow' ],

renderCallback

But not always render.php file is used for dynamic blocks. Sometimes it is just a PHP function. You can find a lot of examples when exploring the code of standard WordPress blocks.

If you’re going to use a function, then first of all we need to specify its name when we registering our block with register_block_type():

add_action( 'init', 'misha_latest_post_block' );
function misha_latest_post_block() {
	register_block_type(
		__DIR__,
		array(
			'render_callback' => 'misha_block_render_callback',
		)
	);
}

// render.php replacement
function misha_block_render_callback( $attributes ) {
	
	$wrapper_attributes = get_block_wrapper_attributes();
	$content = "<div {$wrapper_attributes}>";

	$latest_posts = get_posts( array(
		'posts_per_page' => 1,
		'post_status' => 'publish',
		'fields' => 'ids',
	) );

	if( ! $latest_posts ) {
		$content .= 'No posts';
	}

	$latest_post_id = reset( $latest_posts );

	$content .= sprintf(
		'<a href="%1$s">%2$s</a>',
		esc_url( get_permalink( $latest_post_id ) ),
		esc_html( get_the_title( $latest_post_id ) )
	);
	
	$content .= '</div>';
	return $content;

}

Sometimes register_block_type_from_metadata() is used instead of register_block_type(), don’t pay much attention to it, in our situation the only difference is that the latter is shorter.

Another difference in this approach is that when we use a PHP function instead of render.php file, we need to return the result HTML, but not to print it.

ServerSideRender

As I already said before you don’t have to use this approach when developing custom dynamic blocks but it is useful to know about it, because you will definitely going to meet it when exploring dynamic blocks created by other developers.

import { useBlockProps } from '@wordpress/block-editor';
import ServerSideRender from '@wordpress/server-side-render';

export default function Edit( props ) {

	return (
		<div { ...useBlockProps() }>
			<ServerSideRender
				block="rudr/latest-post"
				attributes={ props.attributes }
			/>
		</div>
	)

}
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X