Gutenberg block development is the modern path for adding custom UI to WordPress. Shortcodes are dead; widgets are deprecated; meta boxes are tolerated only for legacy. Custom blocks are how every serious WordPress feature ships in 2026 — from page builders to WooCommerce checkout to LMS course content.
This guide covers the Gutenberg fundamentals I run on every block project in 2026. block.json registration, dynamic vs static blocks, the edit/save lifecycle, attributes + InspectorControls, and the @wordpress/scripts build toolchain. No stale tutorials referencing PHP-only registration or deprecated meta APIs.
Quick verdict: use create-block scaffold, register via block.json (NOT registerBlockType in JS alone), default to dynamic blocks for anything data-driven, use static blocks for purely-presentational content, and ship blocks in a plugin (NOT in the theme) so they survive theme switches.
Gutenberg block development: quick reference
If you are evaluating Gutenberg block development for your next project, you are weighing real trade-offs between cost, complexity, ownership, and time-to-launch. The right Gutenberg block development decision depends on a handful of variables — team capacity, scope clarity, and how much ongoing maintenance you can absorb. The summary below is the 60-second version; the rest of this guide unpacks the nuance.
- Gutenberg block development pricing typically ranges based on scope clarity, integration count, and ongoing support requirements.
- Gutenberg block development timelines vary from days (small scope) to months (enterprise scope) depending on complexity.
- The biggest variable in Gutenberg block development is requirements clarity at the brief stage — vague briefs produce vague quotes.
- Vendor selection for Gutenberg block development matters more than tool selection — the right team beats the right stack.
- Gutenberg block development ROI is positive when scope is bounded, deliverables are specified, and success criteria are measurable.
For complementary perspectives on Gutenberg block development, the WordPress block editor handbook and Gutenberg block API reference resources cover adjacent angles worth reviewing alongside this guide. They focus on the underlying technology and standards — this post focuses on the Gutenberg block development decision specifically.
When you revisit your Gutenberg block development approach in 12 to 24 months, three signals usually indicate a refresh is justified. First, the original brief no longer matches business reality — product, audience, or operational scope has shifted. Second, the underlying technology has moved forward enough that the Gutenberg block development decision made under previous constraints would be different today. Third, ongoing maintenance overhead has crept up beyond what was forecast at launch. None of these are emergencies on their own; together they signal it is time to revisit fundamentals rather than patch around them.
Static vs dynamic blocks
The most fundamental architectural choice is static vs dynamic. Pick wrong and you fight the framework forever.
Default to dynamic: In 2026, default new blocks to dynamic unless they are purely presentational (e.g., a styled callout, a divider, a typography pattern). Dynamic avoids block validation hell when you ship updates and gives you full PHP access for rendering. The performance argument for static is overstated — full-page caching neutralizes the difference.
Static blocks
Block content saves as serialized HTML in post_content. No PHP runs at render time — the saved HTML displays as-is. Best for: presentational content (typography, layout, styled boxes) where the markup never needs to query data.
- Defined by the
savefunction in JavaScript - Output is fixed at save time
- Faster to render (no PHP execution)
- Cannot show dynamic data (current user, latest posts, etc.)
- Block validation errors when save output changes between block versions
Dynamic blocks
Block stores attributes in post_content; PHP renders the actual output every page load via render_callback. Best for: data-driven content (latest posts, user info, cart count, custom queries) and any block whose output may change without editing the post.
- Defined by
render.phporrender_callback - Output is computed at request time
- Can use any WordPress data — current user, queries, options
- Avoids block validation errors when display logic changes
- Slightly higher TTFB cost (negligible if cached)
block.json — the registration manifest
Since WordPress 5.8, block.json is the canonical block registration. It declares metadata, attributes, supports, and assets.
name— namespaced block name:my-plugin/featuretitle— human-readable name shown in insertercategory— inserter category (text, media, design, widgets, theme, embed, or custom)icon— Dashicon name or SVGattributes— schema for block datasupports— built-in features (color, spacing, typography, alignment)editorScript+editorStyle— assets in editorstyle+script— assets on frontendrender— path to render.php for dynamic blocks
The edit/save lifecycle
Static blocks have two JavaScript functions that determine block behavior:
- edit — what the editor renders. React component receives
attributes+setAttributes - save — what gets serialized to
post_content. Pure function of attributes; no hooks, no state - Save output and database content must match exactly — Gutenberg validates this on every load
- When you change save output across block versions, you need
deprecatedentries for old versions
Attributes — the block data model
Block attributes are the data model. Declared in block.json, accessed via props.attributes, mutated via props.setAttributes.
- type — string, number, boolean, object, array
- default — default value when block is inserted
- source — where to read from in saved markup (attribute, html, query)
- selector — CSS selector for sourcing from saved markup
- For dynamic blocks,
sourcecan be omitted — attributes serialize as JSON in the block delimiter
InspectorControls and BlockControls
The two main UI surfaces for block configuration in the editor.
- InspectorControls — sidebar panels (the right inspector). Use
<PanelBody>with form controls inside - BlockControls — toolbar buttons (above the block). For frequent actions like alignment
- Form controls from
@wordpress/components— TextControl, SelectControl, ToggleControl, RangeControl, etc. - Wrap with
<Fragment>or<>...</>when returning both inspector + block content
block.json supports — get features for free
WordPress provides many block features as opt-in via the supports property. Use them instead of building custom controls.
supports.color— text + background color controlssupports.spacing— padding + margin + blockGap controlssupports.typography— font size, font family, line height controlssupports.align— wide / full alignmentsupports.anchor— HTML anchor (for custom IDs)supports.html— setfalseto disable “Edit as HTML” view
The @wordpress/scripts toolchain
WordPress core provides a build toolchain via @wordpress/scripts. Use it instead of building your own webpack config.
npm i @wordpress/scripts --save-devnpx wp-scripts start— dev mode with watch + sourcemapsnpx wp-scripts build— production buildnpx wp-scripts format— Prettier formatnpx wp-scripts lint-js— ESLint with WordPress confignpx wp-scripts test-unit-js— Jest tests
Where to put your blocks
Blocks should ship in a plugin, not the theme. Theme-locked blocks become orphans on theme switch.
- Plugin scaffolded with
npx @wordpress/create-block my-plugin - Multi-block plugins — each block in its own subdirectory under
blocks/ - Block.json per block, with shared assets in
shared/ - For block-only plugins, the plugin manifest is the create-block scaffold
Common Gutenberg gotchas
Mistakes that look correct but break later:
- Hard-coding strings — wrap all UI strings in
__()from@wordpress/i18n - Forgetting
useBlockProps()— required for proper editor integration in modern blocks - Missing dependencies in useEffect — React strict mode catches this
- Saving HTML that changes between versions — block validation errors in editor; need deprecated entries
- Using global jQuery — Gutenberg is React; do not pollute with jQuery
Architecture — FAQs
Should I use static or dynamic blocks?
Default to dynamic blocks in 2026. Use static blocks ONLY for purely presentational content where the markup is fixed at save time and will never need to query data. Dynamic blocks avoid block validation deprecation pain when you update output and let you use the full WordPress data layer (current user, queries, options).
Can I use TypeScript for Gutenberg blocks?
Yes — @wordpress/scripts supports TypeScript natively. Just rename your .js files to .ts / .tsx and add a tsconfig.json. WordPress core ships TypeScript types for most @wordpress/* packages. The DX is significantly better than untyped JS for non-trivial blocks.
Where should custom blocks live — theme or plugin?
Always plugins. Blocks shipped in a theme become orphans when the user switches themes — their saved content references blocks that no longer exist. Plugins survive theme switches and can be deactivated/reactivated cleanly. The only exception is theme-coupled patterns (block patterns or theme-specific block styles) that genuinely belong with the theme.
Tooling — FAQs
Do I need React knowledge to build Gutenberg blocks?
Yes — Gutenberg is React. You need basic React: components, hooks, JSX. You do NOT need advanced React (Suspense, Server Components, Context for global state). The @wordpress/components library handles most UI primitives so you write less raw React than a typical app.
Should I use create-block or build from scratch?
Use create-block for new plugins. It scaffolds a working block with @wordpress/scripts, block.json, sample edit/save, and the right asset manifest. Build from scratch only if you have an existing plugin you are adding blocks to and you understand the @wordpress/scripts conventions.
How do I version my custom blocks?
block.json supports a version field. Bump it when shipping breaking changes to attributes or save output. For static block save changes, add a deprecated entry that handles old saved markup. For dynamic blocks, version-bump is mostly informational since render.php handles all output.
What is the most important factor in Gutenberg block development?
The single most important factor in Gutenberg block development is matching the project scope to the right delivery model. Gutenberg block development done by the wrong team type can cost 3-5x more than necessary; Gutenberg block development done by the right team is predictable, bounded, and produces measurable value. Run an honest scope discovery before committing to any Gutenberg block development engagement, and insist on detailed deliverables in the SOW so both sides are aligned on what success looks like.
Need custom Gutenberg blocks built right for your project?
A proper custom block uses block.json registration, server-side render where it matters, attribute schemas that prevent invalid markup, and a build pipeline editors actually like. I build Gutenberg blocks with @wordpress/scripts, block-supports for spacing and color, accessibility baked in, and a clean editor experience that matches your design system exactly.

