Guide · Custom Gutenberg blocks

Custom Gutenberg blocks,
without React or a build step.

A custom block needs four things: a name, some fields, an interface for editing them and markup for the front end. WordPress makes you supply all four. GutenFields asks for the first two and generates the third, leaving you one PHP template to write. This page builds a complete block from nothing.

No block.json per block No npm, no bundler WordPress 6.5–7.0
The problem

Why a simple block takes so many files.

Nothing about a testimonial is complicated. Registering one as a native block is, because the block API is built for blocks that need arbitrary JavaScript — and it charges every block that price, including the ones that never needed it.

A block needsNativelyWith GutenFields
A name and metadata A block.json per block, plus registration that reads it Two keys in the field definition
Somewhere to keep the data An attributes map you write and keep in step with the UI Derived from the fields
An editing interface A React edit component, so a build step and a toolchain Generated from the field types
Front-end markup A render.php, or a save function A PHP template in your theme — the one file you write

The interesting column is the third row. A generated interface is possible because a field type already implies its control: a toggle is a switch, a select is a dropdown, an image opens the media library. Write those out as React components thirty times and you have thirty components to maintain; describe them once as data and there is nothing to maintain at all.

Start to finish

A testimonial block, in two files.

This is the whole job. The first file goes anywhere your theme already loads — functions.php, an inc/blocks.php it includes, or an mu-plugin. The second is the markup.

1. Describe the fields

theme/inc/blocks.php
add_action( 'init', function () {
    if ( ! function_exists( 'gutenfields_register_block' ) ) {
        return;
    }

    gutenfields_register_block( [
        'name'   => 'testimonial',          // → block gutenfields/testimonial
        'title'  => 'Testimonial',
        'fields' => [
            [ 'name' => 'quote',  'label' => 'Quote',  'type' => 'rich' ],
            [ 'name' => 'author', 'label' => 'Author', 'type' => 'text' ],
            [ 'name' => 'photo',  'label' => 'Photo',  'type' => 'image' ],
            [ 'name' => 'rated',  'label' => 'Show a rating', 'type' => 'toggle' ],
            [
                'name'  => 'rating',
                'label' => 'Rating',
                'type'  => 'range',
                'min'   => 1,
                'max'   => 5,
                // Only offered once the toggle above is on.
                'conditions' => [
                    [ 'field' => 'rated', 'operator' => '==', 'value' => '1' ],
                ],
            ],
        ],
    ] );
}, 5 ); // before the priority-10 init where blocks are booted

That is the block registered. Reload the editor and Testimonial is in the inserter with a rich-text quote, an author line, a media picker, a switch and a slider that appears only when the switch is on — none of which you have written an interface for.

2. Write the markup

theme/gutenfields/testimonial.php
<figure <?php echo get_block_wrapper_attributes(); ?>>

    <blockquote>
        <?php echo wp_kses_post( gf_field( 'quote' ) ); ?>
    </blockquote>

    <?php if ( $photo = gf_image( 'photo' ) ) : ?>
        <img src="<?php echo esc_url( $photo['url'] ); ?>"
             alt="<?php echo esc_attr( $photo['alt'] ); ?>" />
    <?php endif; ?>

    <figcaption><?php gf_the_field( 'author' ); ?></figcaption>

    <?php // A hidden field keeps its value, so branch on the toggle here. ?>
    <?php if ( gf_field( 'rated' ) ) : ?>
        <p class="stars"><?php echo str_repeat( '★', (int) gf_field( 'rating' ) ); ?></p>
    <?php endif; ?>

</figure>

You can skip the second file to begin with

A block with no template auto-renders one BEM-classed element per field, so it is usable in the editor the moment it is registered. Drop the template in whenever you are ready to take over — or run wp gutenfields scaffold testimonial and have a starter generated from the fields.

Three routes in

Not every block deserves a commit.

A block that a theme depends on belongs in the theme. A block someone asked for on a Thursday afternoon does not, and making a developer open an editor for it is how the list of blocks stops growing. All three routes produce the same kind of block.

Config-as-code

gutenfields_register_block() in your theme. Reviewed in a pull request, deployed with the theme, identical on every environment. This is the one to reach for by default.

The admin field builder

A three-pane screen in wp-admin. Blocks built here live in the database, and Export PHP turns one into the code above the moment it has earned a place in the repository.

JSON import

A portable file matching the published schema — how definitions move between environments, and the format an AI assistant writes when you ask it for a block.

A code block and a saved block that share a name are not a conflict: the code version wins, every time. Prototyping in the builder and then committing the export is a supported path rather than a thing you get away with.

Beyond one of each

Rows, and rows of different shapes.

Most real sections are lists — three features, five logos, an arbitrary stack of page sections. Two field types cover that, and both are in the paid edition.

Repeater Pro

A field whose value is a list of rows, each with the same subfields. The editor gets add, reorder and delete controls for free; the template loops over the rows and reads each one with the gf_sub_* helpers.

Flexible content Pro

A list whose rows may each be a different named layout — a hero, then a pull quote, then a call to action — with its own subfields. Loop it and branch on the layout name the loop hands you.

theme/gutenfields/page-sections.php
<?php // A repeater: every row has the same subfields. ?>
<?php foreach ( gf_repeater( 'features' ) as $row ) : ?>
    <h3><?php gf_the_sub_field( 'title' ); ?></h3>
    <p><?php gf_the_sub_field( 'body' ); ?></p>
<?php endforeach; ?>

<?php // Flexible content: each row names its own layout. ?>
<?php foreach ( gf_flexible( 'sections' ) as $layout => $row ) :
    get_template_part( 'parts/section', $layout );
endforeach; ?>

Containers nest five levels deep, which is a recursion guard rather than a target — the manual explains why two or three is usually the real limit. Options pages, for the settings that belong to the site rather than to any one page, are in the same tier.

Where this sits

Next to the APIs WordPress already gives you.

This is not a replacement for the block editor, and there are jobs it is the wrong tool for. Worth knowing which is which before you commit a theme to it.

block.json and React

Still the right answer for a block that needs behaviour in the editor — a canvas, a custom toolbar, a live-rendering preview. If the block is a form the editor fills in, that machinery is overhead you are paying for nothing.

The Block Bindings API

Added in WordPress 6.5, it connects a core block's attribute to a source such as post meta — a paragraph that shows a custom field. It binds existing blocks; it does not give you a new block with a designed field set. Different job.

Patterns and synced patterns

If the thing you want is a preset arrangement of core blocks, a pattern is simpler than any plugin and costs nothing. Reach for fields when an editor should fill in values, not arrange markup.

ACF Blocks

The nearest neighbour, and the same idea: fields plus a PHP template. The differences are what a field group attaches to and where its values are stored — set out in full here.

Post meta

GutenFields keeps values in the block's attributes, in post content. That makes a block portable between posts, and it means you cannot query on the values. Data the post owns still belongs in meta.

Page builders

There is no drag-and-drop layout canvas here and no styling UI. You get fields, a block and a template; everything about how it looks is your own CSS.

Questions

Building blocks, answered.

Q. Can I really create a custom Gutenberg block without React?
Yes, for the large class of blocks that are a form plus some markup. The editing interface still runs on React — it has to, it is the block editor — but it is one generic component that reads your field definitions, shipped prebuilt inside the plugin. You never write, build or maintain a component. What you write is a PHP array and a PHP template.
Q. Is there a build step, npm install or bundler?
None. The editor bundle ships compiled and is written against the global wp.* objects, so it runs the moment the plugin is activated. The JSX source is in the zip if you would rather build it yourself with @wordpress/scripts, but nothing about installing or using the plugin requires a toolchain.
Q. Where is a block's data stored?
In the block's attributes, in post content — not in post meta. Each field group is a real dynamic block, so the values travel with the block: copy a testimonial into another page and it arrives filled in. The trade is that you cannot query posts by a field value the way post meta allows.
Q. What happens to my content if I deactivate the plugin?
Blocks stop rendering, because the plugin owns the render callback, but nothing is deleted. The markup stays in post content and returns the moment you reactivate. There is no custom table to drop and no orphaned meta to clean up.
Q. Does the editor preview my template?
No. The canvas shows the generated field inputs rather than running your PHP, so the front end is the preview. This is a deliberate limit and the main thing you give up against a block that renders its template in place — worth weighing if editors on your site rely on seeing final styling while they type.
Q. How many field types are there, and which need a licence?
Nineteen types in the free edition — text, textarea, rich, number, range, email, url, select, radio, checkbox, toggle, colour, date, image, file, link, and the post, taxonomy and user relational fields — all with conditional logic. The paid edition adds repeaters, flexible content, the gallery field and options pages. The manual lists every type with its configuration and the helper that reads it.
Q. Can an AI assistant write these definitions for me?
That is what the published JSON schema and the ai-guide command are for. Run wp gutenfields ai-guide to print the whole public API — field types, helpers, file rules — in a form you can paste into an assistant, and it will emit a definitions file the importer accepts.
Get started

Register your first block in about five minutes.

The free edition is a finished plugin, not a trial: nineteen field types, conditional logic, the builder, templates and the CLI, with no expiry. Paste the definition above into your theme and reload the editor.