WordPress Development

How to Build a Custom Gutenberg Block (Without a Page Builder)

What a custom block is made of, static versus dynamic rendering, attributes, the editor interface, and how to keep blocks from breaking after you ship them.

By 6 min read
Code editor window with syntax-highlighted block registration code

Page builders exist because editors need to compose layouts and standard WordPress didn't let them. Custom blocks solve the same problem without the weight, the lock-in or the unconstrained freedom that lets a site drift into eight typefaces.

A block is less code than people expect. Here's what it's actually made of.

The parts of a block

Every block has four pieces, and understanding which does what removes most of the confusion:

  • block.json — the metadata. Name, title, category, icon, the attributes it stores, what it supports, and which scripts and styles it needs. This is the canonical registration since WordPress 5.8.
  • The edit component — a React component that renders the block's interface inside the editor, including its controls.
  • The save function, or a PHP render callback — how the block turns into front-end output.
  • Styles — usually two stylesheets, one shared by editor and front end, one editor-only.

Start with the scaffolding

Don't hand-roll the build setup. The create-block package scaffolds a working block plugin with a build pipeline, block.json, the editor component and the styles already wired together. It saves a day of webpack configuration and gives you the structure the rest of the ecosystem expects.

From there you're editing files rather than assembling a toolchain, which is where the actual work is.

Static versus dynamic rendering

This is the decision that matters most, and it's easy to get wrong in a way you feel six months later.

A static block's save function returns markup, and that markup is stored in the post content. It's fast — no PHP runs on render — and the content is self-contained. The catch is block validation: if you later change the save output, WordPress compares the stored markup against what save now produces, finds a mismatch, and shows the editor an invalidation warning. Fixing that means writing deprecation definitions for every previous version.

A dynamic block stores only its attributes and renders through a PHP callback at request time. Change the rendering and every existing instance updates, with no validation problem and no deprecations. It costs a little PHP on each render.

My default is dynamic for anything I expect to change, and anything that needs live data — a list of recent posts, a price, anything from a query. Static is right for genuinely fixed presentational blocks where the markup will not change.

Attributes: the block's data

Attributes are what the block stores. Declare them in block.json with a type and a default, and where possible a source describing where the value lives in the saved markup.

Keep them minimal and semantic. Store the heading text, the image ID and the chosen layout — not the resulting CSS classes or inline styles. If you store presentation, changing the design later means migrating every instance. If you store meaning, you change the render and you're done.

Store image IDs rather than URLs, so the block can request whichever registered size it needs and survives a change of CDN or domain.

The editor interface

The block editor provides a component library, and using it is what makes a custom block feel native rather than bolted on.

  • InspectorControls puts settings in the sidebar — the right place for options that aren't part of the visible content.
  • BlockControls puts tools in the floating toolbar, for things like alignment.
  • RichText gives you an editable text area with the formatting toolbar, rather than a plain input.
  • MediaUpload opens the real media library, not a URL field.
  • InnerBlocks lets your block contain other blocks, with an allowedBlocks list and a template to constrain what goes inside.
  • The useBlockProps hook wires up the block's wrapper, and omitting it is the most common reason a new block misbehaves in the editor.

Constraining rather than exposing

The temptation with a custom block is to expose every option — colour pickers, font size, padding, alignment — because it feels generous. It's the same mistake page builders make, and it produces the same result.

Offer the choices the design actually supports. Three layout variants rather than free-form columns. The brand palette rather than a colour wheel. A heading level rather than a font size. Editors get the flexibility they need and the site still looks designed in two years.

block.json's supports key is how you do most of this: turn off the built-in colour, spacing and typography controls you don't want, and provide specific alternatives instead.

Block variations and patterns, which are often enough

Before writing a block, check whether you need one. A block variation is a preset of an existing block — the same block with different default attributes and its own name and icon in the inserter. A block pattern is a pre-arranged group of core blocks that an editor inserts and fills in.

A great many 'we need a custom block' requests are actually satisfied by a pattern, which is a file of block markup and no JavaScript at all. Patterns are also much cheaper to maintain, because there's no build step and nothing to deprecate.

Keeping blocks from breaking

  • Prefer dynamic rendering for anything whose markup might change.
  • If you must change a static block's save output, write a deprecation for the old version so existing content keeps validating.
  • Namespace the block properly — my-site/feature-card, not just feature-card — so it can't collide with a plugin's block.
  • Register styles and scripts through block.json so WordPress only loads them on pages where the block appears.
  • Test what happens when an attribute is missing, which is what old content looks like after you add a new attribute.
  • Escape output in the PHP render callback exactly as you would in a template.

When a custom block is the right answer

When editors need to place a specific, designed component repeatedly — a testimonial, a feature grid, a call to action, a pricing table — and you want it to look right every time without them assembling it from primitives.

It's the wrong answer for one-off layouts (use a pattern), for structural page layout in a classic theme (use templates), and for anything that's really content modelling in disguise. A block per content type is usually a sign the data should have been a custom post type with fields.

Frequently asked questions

Do I need to know React to build a Gutenberg block?

Some, but less than you'd think. The edit component is React, and you'll use hooks and JSX, but most of it is composing components the editor already provides rather than writing your own. If you can read JSX and understand state and props, you can build a block. The PHP render callback needs no React at all.

What's the difference between a static and a dynamic block?

A static block saves its markup into the post content, so it's fast but hard to change later — altering the save output invalidates existing content unless you write deprecations. A dynamic block saves only attributes and renders in PHP at request time, so changing the rendering updates every instance instantly.

Why does my block show 'this block contains unexpected or invalid content'?

Because the saved markup in the post no longer matches what the block's save function now produces — you changed the output after content was created. Either revert the change, write a deprecation for the previous version, or convert the block to dynamic rendering so the problem can't recur.

Should I build a custom block or use a block pattern?

Use a pattern when the thing is an arrangement of existing blocks that editors will fill in with their own content. Build a block when it needs its own data, its own controls, or rendering logic. Patterns are just block markup in a file — no build step, nothing to deprecate — so try that first.

Are custom blocks better than a page builder?

For most sites, yes. They're lighter, they don't lock your content into a proprietary format, and you control exactly which options editors get. A page builder's advantage is that it needs no development — which matters if there's no developer, and matters much less if there is.

Topics

  • custom Gutenberg block
  • WordPress block development
  • block editor
  • create-block