Gutenberg blocks are awesome—but sometimes you don’t want to hardcode static content inside save.js
. You want the block to render dynamically, pulling in live data from the database, custom post types, or even external APIs.
In this article, I’ll show you how to create a dynamic Gutenberg block using render_callback
—with real code you can adapt for your own projects.
🧱 What Is a Dynamic Block?
A dynamic block is one that’s rendered by PHP on the frontend instead of being saved as static HTML in the post content. You define a render_callback
, and WordPress will run it each time the block is shown.
Perfect for:
- Post lists
- Custom queries
- Real-time values (dates, stats, user info)
- API-connected content
📁 Folder Structure
Here’s how I typically organize a dynamic block inside my plugin:
my-plugin/
├── blocks/
│ └── latest-posts/
│ ├── block.json
│ ├── edit.js
│ ├── index.js
│ ├── style.scss
│ └── render.php
📝 Step 1: block.json
{
"apiVersion": 2,
"name": "myplugin/latest-posts",
"title": "Latest Posts",
"category": "widgets",
"icon": "megaphone",
"supports": { "html": false },
"attributes": {
"postsToShow": {
"type": "number",
"default": 3
}
},
"editorScript": "file:./index.js",
"style": "file:./style.css",
"render": "file:./render.php"
}
🧠 Step 2: edit.js
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit({ attributes, setAttributes }) {
return (
<>
<InspectorControls>
<PanelBody title={__('Block Settings')}>
<RangeControl
label={__('Number of posts')}
value={attributes.postsToShow}
onChange={(value) => setAttributes({ postsToShow: value })}
min={1}
max={10}
/>
</PanelBody>
</InspectorControls>
<p>{__('This block shows the latest posts on the frontend.')}</p>
</>
);
}
🧩 Step 3: render.php
<?php
$atts = $attributes ?? [];
$posts_to_show = isset($atts['postsToShow']) ? intval($atts['postsToShow']) : 3;
$args = [
'post_type' => 'post',
'posts_per_page' => $posts_to_show,
];
$posts = get_posts($args);
if (!$posts) {
return '<p>No posts found.</p>';
}
$output = '<ul class="latest-posts">';
foreach ($posts as $post) {
$output .= sprintf(
'<li><a href="%s">%s</a></li>',
esc_url(get_permalink($post)),
esc_html(get_the_title($post))
);
}
$output .= '</ul>';
echo $output;
🧱 Step 4: Register the Block
function myplugin_register_blocks() {
register_block_type(__DIR__ . '/blocks/latest-posts');
}
add_action('init', 'myplugin_register_blocks');
🧪 Bonus Ideas
- Add a taxonomy filter (category dropdown)
- Make it support custom post types
- Return results as cards with Flexbox or CSS Grid
- Output structured HTML + schema for SEO
✅ Final Thoughts
Once you’ve built one dynamic block, the possibilities open up. You can turn Gutenberg into a full content delivery system—with live queries, third-party data, and reusable frontend logic.
And the best part? It’s all native WordPress. No shortcode spaghetti. No plugin bloat.
Leave a Reply