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' => truefor HTML anchor links - Color
'color' => ['text' => true, 'background' => true] - Typographyfont size, line height controls
- Spacingpadding, margin controls
- Inner blocksnest other Gutenberg blocks inside
- Alignment wideenable wide and full alignments
- Multiplewhether the block can be used multiple times on a page
- Reusableallow 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:
- Install Field Forge alongside ACF
- Run the field group + value import
- Deactivate ACF
- Field Forge’s ACF compat layer takes over
- Your
acf_register_block_type()calls continue to work - 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?
PHP blocks support is included in every paid plan.