Dynamic blocks represent a powerful evolution in WordPress block development, enabling server-side rendering that displays real-time data, database queries, and user-specific content. This guide teaches you to build dynamic blocks that combine the flexibility of Gutenberg with the power of PHP.
Static vs Dynamic Blocks
Understanding the difference between static and dynamic blocks is fundamental.
Static blocks save their content directly to the database as HTML. When you create a paragraph block, WordPress stores the exact HTML in post_content. This is efficient for content that doesn’t change.
Dynamic blocks save only their attributes to the database. Each time the page loads, WordPress executes a PHP render callback that generates the HTML. This enables real-time data like “latest posts,” user-specific content, and database queries.
Use static blocks for fixed content. Use dynamic blocks when content must update automatically based on data changes, user context, or external sources.
When to Use Dynamic Blocks
Dynamic blocks excel in specific scenarios:
- Latest content displays – Showing recent posts, comments, or custom post types
- User-specific content – Displaying personalized data based on logged-in users
- External data – Fetching and displaying API data, weather, stock prices
- Database queries – Running complex WP_Query operations
- Real-time calculations – Computing values based on current data
Avoid dynamic blocks for purely presentational content that never changes. The server-side rendering adds processing overhead unnecessary for static content.
Creating Your First Dynamic Block
Start with block.json to define your block:
{
"apiVersion": 3,
"name": "dprt/recent-posts",
"title": "Recent Posts",
"category": "widgets",
"icon": "list-view",
"description": "Display recent posts dynamically",
"attributes": {
"numberOfPosts": {
"type": "number",
"default": 5
},
"postType": {
"type": "string",
"default": "post"
}
},
"editorScript": "file:./index.js",
"editorStyle": "file:./editor.css",
"style": "file:./style.css"
}Register the block with a render callback:
function dprt_register_recent_posts_block() {
register_block_type( __DIR__ . '/build/blocks/recent-posts', array(
'render_callback' => 'dprt_render_recent_posts_block'
) );
}
add_action( 'init', 'dprt_register_recent_posts_block' );Implementing the Render Callback
The render callback function generates your block’s HTML:
function dprt_render_recent_posts_block( $attributes, $content, $block ) {
$number_of_posts = isset( $attributes['numberOfPosts'] ) ? absint( $attributes['numberOfPosts'] ) : 5;
$post_type = isset( $attributes['postType'] ) ? sanitize_text_field( $attributes['postType'] ) : 'post';
$args = array(
'post_type' => $post_type,
'posts_per_page' => $number_of_posts,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC'
);
$query = new WP_Query( $args );
if ( ! $query->have_posts() ) {
return '<p>' . esc_html__( 'No posts found.', 'dprt' ) . '</p>';
}
$output = '<div class="wp-block-dprt-recent-posts">';
$output .= '<ul>';
while ( $query->have_posts() ) {
$query->the_post();
$output .= '<li>';
$output .= '<a href="' . esc_url( get_permalink() ) . '">';
$output .= esc_html( get_the_title() );
$output .= '</a>';
$output .= '<span class="post-date"> - ' . esc_html( get_the_date() ) . '</span>';
$output .= '</li>';
}
$output .= '</ul>';
$output .= '</div>';
wp_reset_postdata();
return $output;
}Always sanitize attributes and escape output to prevent security vulnerabilities.
Editor Controls and Preview
Create InspectorControls for block settings:
import { useBlockProps, InspectorControls } from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import ServerSideRender from "@wordpress/server-side-render";
export default function Edit({ attributes, setAttributes }) {
const { numberOfPosts, postType } = attributes;
return (
<>
<InspectorControls>
<PanelBody title="Settings">
<RangeControl
label="Number of posts"
value={numberOfPosts}
onChange={(value) => setAttributes({ numberOfPosts: value })}
min={1}
max={20}
/>
<SelectControl
label="Post Type"
value={postType}
options={[
{ label: "Posts", value: "post" },
{ label: "Pages", value: "page" }
]}
onChange={(value) => setAttributes({ postType: value })}
/>
</PanelBody>
</InspectorControls>
<div {...useBlockProps()}>
<ServerSideRender block="dprt/recent-posts" attributes={attributes} />
</div>
</>
);
}ServerSideRender displays a live preview in the editor by calling your render callback via AJAX.
Performance Optimization with Caching
Dynamic blocks can impact performance. Implement caching for expensive operations:
function dprt_render_recent_posts_block( $attributes, $content, $block ) {
$number_of_posts = isset( $attributes['numberOfPosts'] ) ? absint( $attributes['numberOfPosts'] ) : 5;
// Create unique cache key based on attributes
$cache_key = 'dprt_recent_posts_' . md5( serialize( $attributes ) );
// Try to get cached result
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Generate output (expensive operation)
$output = dprt_generate_recent_posts_html( $number_of_posts );
// Cache for 1 hour
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
return $output;
}Clear the cache when content updates:
function dprt_clear_recent_posts_cache( $post_id ) {
// Clear all recent posts caches
global $wpdb;
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_dprt_recent_posts_%'" );
}
add_action( 'save_post', 'dprt_clear_recent_posts_cache' );User-Specific Content
Display personalized content based on the logged-in user:
function dprt_render_user_dashboard_block( $attributes, $content, $block ) {
if ( ! is_user_logged_in() ) {
return '<p>' . esc_html__( 'Please log in to view your dashboard.', 'dprt' ) . '</p>';
}
$current_user = wp_get_current_user();
$user_posts = count_user_posts( $current_user->ID );
$output = '<div class="user-dashboard">';
$output .= '<h3>' . sprintf( esc_html__( 'Welcome, %s', 'dprt' ), esc_html( $current_user->display_name ) ) . '</h3>';
$output .= '<p>' . sprintf( esc_html__( 'You have published %d posts.', 'dprt' ), $user_posts ) . '</p>';
$output .= '</div>';
return $output;
}External API Integration
Fetch and display external data:
function dprt_render_weather_block( $attributes, $content, $block ) {
$city = isset( $attributes['city'] ) ? sanitize_text_field( $attributes['city'] ) : 'London';
$cache_key = 'dprt_weather_' . sanitize_title( $city );
$weather_data = get_transient( $cache_key );
if ( false === $weather_data ) {
$api_url = 'https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=' . urlencode( $city );
$response = wp_remote_get( $api_url );
if ( is_wp_error( $response ) ) {
return '<p>' . esc_html__( 'Unable to fetch weather data.', 'dprt' ) . '</p>';
}
$weather_data = json_decode( wp_remote_retrieve_body( $response ), true );
set_transient( $cache_key, $weather_data, 30 * MINUTE_IN_SECONDS );
}
$temp = isset( $weather_data['current']['temp_c'] ) ? $weather_data['current']['temp_c'] : 'N/A';
return '<div class="weather-block">' .
'<p>' . esc_html( $city ) . ': ' . esc_html( $temp ) . '°C</p>' .
'</div>';
}Error Handling
Implement robust error handling:
function dprt_render_dynamic_block( $attributes, $content, $block ) {
try {
// Validate attributes
if ( ! isset( $attributes['requiredField'] ) ) {
throw new Exception( 'Required field missing' );
}
// Attempt operation
$result = dprt_perform_operation( $attributes );
if ( false === $result ) {
throw new Exception( 'Operation failed' );
}
return $result;
} catch ( Exception $e ) {
// Log error
error_log( 'Dynamic block error: ' . $e->getMessage() );
// Return user-friendly message
return '<p class="block-error">' . esc_html__( 'Content temporarily unavailable.', 'dprt' ) . '</p>';
}
}Conclusion
Dynamic blocks combine Gutenberg’s user-friendly interface with PHP’s server-side power. Use them for real-time data, personalization, and database queries. Implement caching for performance, validate all inputs, escape all outputs, and handle errors gracefully. Start with simple dynamic blocks and gradually build more complex functionality as you master the concepts.
External Links
- Dynamic Blocks Documentation
- register_block_type() Reference
- ServerSideRender Component
- Block Metadata (block.json)
- WordPress Query Functions
Call to Action
Streamline your workflow! Block Editor Navigator Pro provides instant block navigation, search, and organization. Find any block in seconds—try it free!

