How to create a block that renders on server with render_callback (PHP) in WordPress

Contents

Overview: Server-rendered blocks with render_callback in WordPress

This article is a comprehensive, step-by-step guide to creating a Gutenberg block that is rendered on the server using a PHP render_callback. It covers block metadata, editor JavaScript, registering the block in PHP, secure output, passing attributes, rendering inner blocks, caching strategies, and best practices for performance and security. Example code blocks are included and are ready to drop into a plugin or theme.

Why use server-side rendering?

  • Dynamic content: The block output depends on current server state (post meta, custom queries, user state, dates, transient values).
  • Centralized rendering logic: Complex HTML generation and escaping is easier and safer in PHP.
  • SEO caching: HTML is available on first load CDNs and page caches can cache final HTML.
  • Consistent output on front and editor: The editor can preview server output (via ServerSideRender) and the front always uses the same renderer.

High-level workflow

  1. Create block metadata (block.json) or register your block using register_block_type.
  2. Create an editor script that registers the block type in JavaScript and sets save() to return null.
  3. Register the block server-side in PHP and provide a render_callback function that outputs the final HTML.
  4. Secure and sanitize all attributes and output with esc_ and wp_kses_ functions.
  5. Optionally implement caching (transients/object cache) and invalidation logic.

Minimum example structure

A simple plugin folder layout:

  • my-plugin-block/
    • block.json
    • build/ (editor script and asset manifest)
    • src/editor.js
    • render.php (optional or register via PHP)
    • my-plugin-block.php (main plugin bootstrap)

block.json (API v2) example

This block.json registers editor script and marks the block as server-rendered by making save return null in JS. Optionally you can reference a PHP render file using render: file:./render.php and use register_block_type_from_metadata() in PHP.

{
  apiVersion: 2,
  name: my-plugin/server-rendered-block,
  title: Server-rendered Block,
  category: widgets,
  icon: admin-site,
  description: An example server-rendered block using a PHP render_callback.,
  attributes: {
    title: {
      type: string,
      default: Hello from PHP
    },
    postId: {
      type: integer,
      default: 0
    }
  },
  supports: {
    html: false
  },
  editorScript: file:./build/index.js,
  style: file:./build/style.css
}

Editor JavaScript: register the block and make save return null

In the editor, blocks that use server rendering should have a save function that returns null. You can also present a live preview in the editor using the ServerSideRender component from @wordpress/server-side-render or fetch rendered HTML via a REST call.

/
  src/editor.js
 /
import { registerBlockType } from @wordpress/blocks
import { TextControl } from @wordpress/components
import { useState } from @wordpress/element
import ServerSideRender from @wordpress/server-side-render

registerBlockType( my-plugin/server-rendered-block, {
  edit: ( props ) => {
    const { attributes, setAttributes } = props
    const { title, postId } = attributes

    return (
      
setAttributes( { title: val } ) } />
) }, // Save returns null — block is rendered dynamically on the server. save: () => null, } )

Enqueueing and registering the editor script

Use the build output and the asset manifest (if created by @wordpress/scripts) to enqueue editor script and styles. The PHP registration will also register the block type.

// my-plugin-block.php (main plugin file)

Registering with explicit render_callback in PHP

If you prefer to register the block programmatically and pass a render_callback, use register_block_type and include a callable for render_callback. This example uses attributes passed from the editor.

 my-plugin-block-editor-script, // must be previously registered
        render_callback => my_plugin_render_server_block,
        attributes      => array(
            title  => array( type => string, default => Hello from PHP ),
            postId => array( type => number, default => 0 ),
        ),
    ) )
}
add_action( init, my_plugin_register_server_block )

function my_plugin_render_server_block( attributes, content, block ) {
    // attributes: attributes as sent from the editor
    // content: innerBlocks saved content (if any)
    // block: full block object, includes context and clientId

    title = isset( attributes[title] ) ? sanitize_text_field( attributes[title] ) : 
    post_id = isset( attributes[postId] ) ? absint( attributes[postId] ) : get_the_ID()

    // Build HTML safely using escaping
    html = 
html .=

. esc_html( title ) .

if ( post_id ) { post = get_post( post_id ) if ( post ) { html .=
html .= wp_kses_post( wpautop( post->post_content ) ) html .=
} } html .=
return html } ?>

Alternative: using block metadata with render file

If your block.json contains a render: file:./render.php (or you include a render.php in the block dir) and you call register_block_type_from_metadata() (or register_block_type(__DIR__)), WordPress will look for that render file. The render file should return the final HTML or callable.

 . esc_html( title ) . 
} ?>

Security and escaping

Passing context from the editor to the server

Attributes are serialized in the post content. If you need additional context (current user ID, theme settings), you can:

Inner blocks and the content parameter

If your block allows inner blocks, the dynamic blocks content parameter will contain the raw content of the inner blocks (their saved markup). To properly render nested blocks on the server, you can call render_block or do_blocks as appropriate.

 . rendered_inner . 
}
?>

Using ServerSideRender in the editor

ServerSideRender makes a REST request to the block-renderer endpoint to get server HTML in the editor preview. This helps authors preview real output while editing.

import ServerSideRender from @wordpress/server-side-render


Caching output for performance

Server rendering can be heavier than static HTML. Use caching when the output is expensive to generate. Keep caches attribute- and context-aware, and invalidate when needed.


    html .= 

. esc_html( attributes[title] ?? ) .

// ... expensive DB queries, remote API calls, etc. html .= // Cache for 1 hour set_transient( cache_key, html, HOUR_IN_SECONDS ) return html } ?>

Invalidate the transient when relevant data changes (e.g., on save_post, on remote API webhook, or when a referenced post updates).

Dealing with user capabilities and personalization

Error handling and graceful fallback

Example: full plugin minimal implementation

Below is a compact plugin that registers a server-rendered block, enqueues an editor script, and implements a safe render_callback.

 esrb-editor,
        render_callback => esrb_render_callback,
        attributes      => array(
            title => array( type => string, default => Dynamic Title ),
        ),
    ) )
}
add_action( init, esrb_register_block )

function esrb_render_callback( attributes, content ) {
    title = isset( attributes[title] ) ? sanitize_text_field( attributes[title] ) : 

    ob_start()
    ?>
    

. esc_html( get_the_title( post ) ) .

} ?>

Debugging tips

Advanced topics

Checklist before shipping

  1. All attributes are sanitized on input and escaped on output.
  2. Cache keys include attributes and any context that affects output.
  3. Block saves as dynamic (save returns null) so front rendering always uses PHP.
  4. Editor preview uses ServerSideRender or a reliable approximation.
  5. Styles and editor assets are properly enqueued and registered in block metadata or PHP.
  6. Consider accessibility, ARIA roles, and semantic HTML in the rendered output.

Useful references

Final notes

Server-side rendering with render_callback is a powerful pattern for blocks whose output depends on server-side data or complex logic. Keep rendering logic secure, minimize expensive operations, and apply caching where appropriate. The patterns shown here — registering blocks, returning null from save(), and implementing secure render callbacks — form a solid foundation for production-ready, server-rendered Gutenberg blocks.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *