PHP Gutenberg Blocks for WordPress | Field Forge (ACF-Compatible)
Download Log in

PHP-Rendered Gutenberg Blocks — acf_register_block_type Compatible

Field Forge supports registering Gutenberg blocks that render via PHP callback functions, with full access to custom fields inside the callback. The registration function is acf_register_block_type() (same as ACF, for zero-friction migration) or fieldforge_register_block_type() (native). Either way, you get the exact same workflow Advanced Custom Fields popularized — but on top of Field Forge’s custom table storage and AI features.


Why PHP-rendered blocks?

Gutenberg (WordPress’s block editor) was designed around JavaScript-rendered blocks. You write a save() function in JS that returns the block’s HTML, and WordPress saves that HTML in the post content. For simple visual blocks this works great.

But for dynamic blocks — “Show latest 3 blog posts with custom styling,” “Display a team member with data from the current user’s profile,” “Render a product grid from a WooCommerce query” — JavaScript blocks are painful. You end up writing REST API calls, managing loading states, and rebuilding data-fetching logic that WordPress already knows how to do in PHP.

PHP-rendered blocks let you write a PHP callback that runs at page render time, has full access to WordPress’s data APIs (WP_Query, get_field(), get_post_meta(), etc.), and returns the HTML directly. Simpler, faster, and more WordPress-idiomatic.

ACF introduced acf_register_block_type() years ago and it became the de facto standard for agency developers building custom Gutenberg blocks. Field Forge supports the same function signature so migration is trivial.


How to register a PHP block

Basic example

// functions.php or a custom plugin

add_action('acf/init', 'register_my_blocks');

function register_my_blocks() {
    if (function_exists('acf_register_block_type')) {
        acf_register_block_type([
            'name'            => 'feature-card',
            'title'           => __('Feature Card'),
            'description'     => __('A feature card with icon, title, and description.'),
            'render_callback' => 'render_feature_card_block',
            'category'        => 'theme',
            'icon'            => 'star-filled',
            'keywords'        => ['feature', 'card'],
            'supports'        => [
                'align' => ['wide', 'full'],
                'anchor' => true,
                'color' => true,
            ],
        ]);
    }
}

function render_feature_card_block($block) {
    $icon = get_field('icon');
    $title = get_field('title');
    $description = get_field('description');
    ?>
    <div class="feature-card">
        <div class="feature-card__icon"><?php echo esc_html($icon); ?></div>
        <h3 class="feature-card__title"><?php echo esc_html($title); ?></h3>
        <p class="feature-card__description"><?php echo esc_html($description); ?></p>
    </div>
    <?php
}

This code is identical to ACF’s registration code. Works unchanged after migration.

Native Field Forge registration

If you prefer Field Forge’s native API (for code cleanliness or to avoid implying ACF is still needed), use fieldforge_register_block_type():

add_action('init', function() {
    fieldforge_register_block_type([
        'name'            => 'feature-card',
        'title'           => __('Feature Card'),
        'render_callback' => 'render_feature_card_block',
        'category'        => 'theme',
    ]);
});

Both functions do the same thing. Use whichever fits your team’s conventions.


Custom fields in the block

Each PHP block can have its own field group assigned via location rules:

Location Rules:
  Block is equal to "acf/feature-card"

When an editor adds the block to a post, Field Forge renders the field group in the block’s sidebar (or inline, depending on block settings). The editor fills in the fields, the block auto-updates with live preview.

At render time, the render_callback function uses get_field() to access the field values:

function render_feature_card_block($block) {
    // get_field() works inside block callbacks
    $icon = get_field('icon');
    $title = get_field('title');

    // The $block array contains everything about this specific block instance
    $block_id = $block['id'];
    $is_preview = !empty($block['is_preview']);
    $block_data = $block['data'];
    $className = !empty($block['className']) ? $block['className'] : '';

    // Render HTML
    ?>
    <div class="feature-card <?php echo esc_attr($className); ?>">
        <h3><?php echo esc_html($title); ?></h3>
    </div>
    <?php
}

Block supports

PHP blocks support all standard Gutenberg block features:

  • Alignment'align' => ['wide', 'full']
  • Anchor'anchor' => true for HTML anchor links
  • Color'color' => ['text' => true, 'background' => true]
  • Typography — font size, line height controls
  • Spacing — padding, margin controls
  • Inner blocks — nest other Gutenberg blocks inside
  • Alignment wide — enable wide and full alignments
  • Multiple — whether the block can be used multiple times on a page
  • Reusable — allow saving as a reusable block

All configured via the standard WordPress block support API.


Preview mode

When an editor is viewing the block in the editor (not on the frontend), the callback runs in “preview mode”:

function render_my_block($block) {
    if ($block['is_preview']) {
        // Rendering in the editor
        echo '<div class="preview-wrapper">';
        render_actual_block($block);
        echo '</div>';
    } else {
        render_actual_block($block);
    }
}

Use this to add editor-only wrappers, placeholder content when fields are empty, or special preview styling.


Inner blocks

PHP blocks can contain other Gutenberg blocks via InnerBlocks:

acf_register_block_type([
    'name' => 'two-column',
    'render_callback' => 'render_two_column_block',
    'supports' => [
        'inner_blocks' => true,
    ],
]);

function render_two_column_block($block) {
    ?>
    <div class="two-column">
        <div class="column">
            <InnerBlocks />
        </div>
        <div class="column">
            <!-- Static content or another InnerBlocks -->
        </div>
    </div>
    <?php
}

This lets you build reusable layout blocks that users can fill with any core Gutenberg blocks (paragraph, heading, image, etc.).


Block template for initial content

Pre-populate blocks with default inner content:

acf_register_block_type([
    'name' => 'three-column',
    'render_callback' => 'render_three_column_block',
    'template' => [
        ['core/columns', [], [
            ['core/column', [], [['core/heading', ['content' => 'Column 1']]]],
            ['core/column', [], [['core/heading', ['content' => 'Column 2']]]],
            ['core/column', [], [['core/heading', ['content' => 'Column 3']]]],
        ]],
    ],
]);

Migrating ACF block code

Every acf_register_block_type() call in your existing theme works unchanged when Field Forge is active (via the compatibility layer). No code changes needed.

The migration process:

  1. Install Field Forge alongside ACF
  2. Run the field group + value import
  3. Deactivate ACF
  4. Field Forge’s ACF compat layer takes over
  5. Your acf_register_block_type() calls continue to work
  6. Field groups assigned to blocks via location rules are migrated

Most sites with custom PHP blocks complete migration without touching block registration code.


Performance

PHP blocks render on the server, so they don’t add JavaScript bundle weight. Combined with Field Forge’s custom table storage, field value lookups inside render callbacks are fast. A page with 20 custom blocks runs in a fraction of the query count a similar JavaScript-rendered setup would need.


Comparison with JavaScript Gutenberg blocks

PHP-rendered blocks JavaScript Gutenberg blocks
Language PHP JavaScript (React / JSX)
Build tooling None Webpack / Vite / wp-scripts
Dynamic data access Easy (get_field(), WP_Query) Requires REST API calls
Developer experience Familiar to PHP devs Requires JavaScript skills
Client-side interactivity Not possible Full React component lifecycle
Server-side rendering Yes (default) Dynamic blocks only
Best for Content blocks, dynamic queries, custom fields Interactive widgets, live editing

PHP blocks and JavaScript blocks aren’t mutually exclusive — use both on the same site for different purposes.


Ready to build Gutenberg blocks with custom fields?

Get Field Forge — from $35/year →

PHP blocks support is included in every paid plan.

Forge AI Assistant Online

Hi! I'm the Field Forge AI assistant. Ask me anything about the plugin — setup, features, troubleshooting, or development.

Just now
Powered by Forge AI · Browse docs