Dynamic blocks
Create your first dynamic block
Build a complete Hero block in a theme: registration, fields, secure PHP rendering, CSS, editor use, and saving.
#What you will build
The finished Hero block has editable heading, summary, image, button, and alignment fields. WordPress saves those values with the page, while render.php creates the frontend HTML on every render.
#1. Create this folder structure
your-theme/
├── functions.php
├── inc/
│ └── voxycure.php
└── blocks/
└── hero/
├── render.php
└── style.cssUse the active theme or child theme. The examples below assume these exact paths.
#2. Load the Voxycure definitions
Add this once to the theme’s functions.php. get_theme_file_path() returns an absolute, child-theme-aware path.
<?php
defined('ABSPATH') || exit;
require_once get_theme_file_path('inc/voxycure.php');#3. Register the block and its stylesheet
Paste this complete file into inc/voxycure.php. The registration callback receives $registry from Voxycure; you do not create the object yourself.
<?php
defined('ABSPATH') || exit;
use Voxyframe\Core\Registry;
add_action('voxycure/register', function (Registry $registry): void {
$registry->register_block('hero', [
'label' => 'Hero',
'description' => 'Primary page introduction.',
'category' => 'design',
'icon' => 'cover-image',
'keywords' => ['banner', 'header'],
'template' => get_theme_file_path('blocks/hero/render.php'),
'editor_scope' => ['post_types' => ['page']],
'fields' => [
[
'field_key' => 'heading',
'label' => 'Heading',
'type' => 'text',
'default_value' => 'Build something useful',
],
[
'field_key' => 'summary',
'label' => 'Summary',
'type' => 'textarea',
],
[
'field_key' => 'image',
'label' => 'Image',
'type' => 'image',
],
[
'field_key' => 'primary_cta',
'label' => 'Primary action',
'type' => 'link_button',
],
[
'field_key' => 'alignment',
'label' => 'Text alignment',
'type' => 'select',
'default_value' => 'left',
'location' => 'inspector',
'options' => [
['value' => 'left', 'label' => 'Left'],
['value' => 'center', 'label' => 'Center'],
],
],
],
]);
});
add_action('after_setup_theme', function (): void {
wp_enqueue_block_style('voxyframe/hero', [
'handle' => 'my-theme-voxycure-hero',
'src' => get_theme_file_uri('blocks/hero/style.css'),
'path' => get_theme_file_path('blocks/hero/style.css'),
'ver' => wp_get_theme()->get('Version'),
]);
});#Registration fields explained
| Key | Value in this guide | Purpose |
|---|---|---|
hero | Stable block ID | Creates the WordPress block name voxyframe/hero. |
label | Hero | Name developers and editors see in the inserter. |
description | Short explanation | Helps editors choose the correct block. |
category | design | Places it in the Design inserter category. |
icon | cover-image | Uses a WordPress Dashicon slug. |
keywords | banner, header | Additional inserter search terms. |
template | Absolute PHP path | The trusted PHP file that renders the block. |
editor_scope | Pages | Offers this Hero only in the block editor for the page post type. |
fields | Field definitions | Builds editor controls and the block attribute schema. |
location | inspector | Moves alignment to the editor settings sidebar; other fields remain in the block. |
#Choose where the block appears
category only groups a block inside the inserter. It does not make a block a page, product, or WooCommerce block. Use editor_scope to control which WordPress editors offer it.
| Scope key | Matches |
|---|---|
post_types | Post, page, product, or any public custom post-type slug. |
post_ids | Exact numeric post or page IDs. |
post_slugs | Exact post slugs. |
template_slugs | A template post such as single-product, when WordPress provides that template post in the editor context. |
editor_contexts | core/edit-post, core/edit-site, or another WordPress block-editor context. |
// Every page, but not posts or other custom post types.
'editor_scope' => [
'post_types' => ['page'],
],
// Only one landing page. Conditions in one rule use AND logic.
'editor_scope' => [
'post_types' => ['page'],
'post_ids' => [42],
],
// Projects OR products. Rules in a list use OR logic.
'editor_scope' => [
['post_types' => ['project']],
['post_types' => ['product']],
],#4. Create the PHP render file
Paste this into blocks/hero/render.php. The framework provides $attributes; each array key matches a registered field_key.
<?php
/**
* Hero dynamic block template.
*
* Available variables:
* - $attributes: Saved block field values.
* - $block_id: Voxycure block ID ("hero").
* - $block_title: Human-readable block label.
*/
defined('ABSPATH') || exit;
$heading = (string) ($attributes['heading'] ?? '');
$summary = (string) ($attributes['summary'] ?? '');
$image = (array) ($attributes['image'] ?? []);
$link = (array) ($attributes['primary_cta'] ?? []);
$alignment = (string) ($attributes['alignment'] ?? 'left');
if (!in_array($alignment, ['left', 'center'], true)) {
$alignment = 'left';
}
?>
<section <?= get_block_wrapper_attributes([
'class' => 'vc-hero vc-hero--' . $alignment,
]) ?>>
<div class="vc-hero__content">
<?php if ($heading !== '') : ?>
<h2 class="vc-hero__heading"><?= esc_html($heading) ?></h2>
<?php endif; ?>
<?php if ($summary !== '') : ?>
<p class="vc-hero__summary"><?= esc_html($summary) ?></p>
<?php endif; ?>
<?php if (!empty($link['url']) && !empty($link['text'])) : ?>
<a class="vc-hero__button" href="<?= esc_url($link['url']) ?>">
<?= esc_html($link['text']) ?>
</a>
<?php endif; ?>
</div>
<?php if (!empty($image['id'])) : ?>
<div class="vc-hero__media">
<?= wp_get_attachment_image((int) $image['id'], 'large') ?>
</div>
<?php endif; ?>
</section>#Why the render code is safe
esc_html()escapes visible text.esc_url()escapes the link destination.wp_get_attachment_image()renders responsive WordPress image markup from a trusted attachment ID.- The alignment is checked against an allow-list before becoming a CSS class.
get_block_wrapper_attributes()preserves WordPress-generated block classes and attributes.
#5. Add the block styles
Paste this into blocks/hero/style.css. The earlier wp_enqueue_block_style() call makes it available in both the editor and frontend; themes that load block assets on demand can load it only when the Hero is rendered.
.vc-hero {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(18rem, .8fr);
gap: clamp(2rem, 6vw, 6rem);
align-items: center;
padding-block: clamp(3rem, 8vw, 7rem);
}
.vc-hero--center .vc-hero__content {
text-align: center;
}
.vc-hero__heading {
margin: 0;
font-size: clamp(2.5rem, 6vw, 5.5rem);
line-height: 1;
}
.vc-hero__summary {
max-width: 42rem;
font-size: 1.125rem;
}
.vc-hero__button {
display: inline-flex;
padding: .8rem 1.2rem;
border-radius: .4rem;
color: #fff;
background: #1d49c3;
text-decoration: none;
}
.vc-hero__media img {
display: block;
width: 100%;
height: auto;
border-radius: 1rem;
}
@media (max-width: 700px) {
.vc-hero {
grid-template-columns: 1fr;
}
}#6. Insert and save the block
- Open Pages → Add New or edit an existing page.
- Click the + block inserter and search for Hero, banner, or header.
- Enter the heading and summary, choose an image, and fill in the button.
- Open the block settings sidebar to choose text alignment.
- Click Publish or Update, then view the page.
#How the values are saved
Each field becomes a typed block attribute. The Voxycure editor calls WordPress setAttributes(); WordPress serializes the attributes into the block comment inside post_content. Autosaves, revisions, undo, and the normal page update flow continue to work. Voxycure does not submit a second request or store the values in a custom table.
#Change an existing block later
- Markup change: edit
render.php; all existing Hero blocks use it immediately. - Design change: edit
style.css. - New field: add its definition and read the new key with a fallback in
render.php. - Removed field: stop rendering it first. Old saved attributes may remain harmlessly in page content.
#If the block does not appear
- Confirm Voxycure Framework is installed and active.
- Confirm
functions.phploadsinc/voxycure.php. - Do not place the registration inside
init; usevoxycure/register. - Confirm
blocks/hero/render.phpexists and the filename case matches. - Open the browser console and WordPress debug log for PHP or JavaScript errors.
#Official WordPress references
- Creating dynamic blocks — how blocks are rendered by PHP in WordPress.
- Registration of a block — current native registration guidance.
- get_theme_file_path() — resolve theme files safely.
#When to use block.json instead
Voxycure is well suited to PHP-rendered theme sections with shared fields. Use a native block.json build when a block needs custom editor JavaScript, variations, bindings, advanced supports, or the Interactivity API.