A gallery block is the canonical “real” Gutenberg block. It has attributes (which images, what layout), it renders dynamically (the markup is generated, not just saved static HTML), and it needs frontend interactivity (the lightbox, maybe filtering). Build one properly and you understand the modern block-development model end to end, because a gallery exercises every part of it. This is that build, 2026-current: block.json as the source of truth, dynamic rendering for SEO, the Interactivity API for frontend behaviour, and the newer APIs worth knowing.
It is the block-building deep-dive of our developer’s guide to building galleries.
block.json: the single source of truth
Modern block development starts and centres on block.json. It declares the block to WordPress, defines its attributes, and — importantly — auto-enqueues the JS and CSS you point it at, so you no longer hand-wire wp_enqueue_script for block assets. A minimal gallery block’s metadata:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "myplugin/gallery",
"title": "Gallery",
"category": "media",
"attributes": {
"ids": { "type": "array", "default": [] },
"layout": { "type": "string", "default": "grid" }
},
"editorScript": "file:./index.js",
"style": "file:./style.css",
"render": "file:./render.php"
}
The two attributes (ids and layout) are the block’s saved state; the render key pointing at a PHP file is what makes this a dynamic block, covered next. Scaffold a new block with @wordpress/create-block (it generates this structure for you) and build with @wordpress/scripts (zero-config webpack for blocks). Starting from block.json rather than hand-wiring is the single biggest modernisation if you learned block development a few years ago.
Static vs dynamic blocks
The fundamental fork. A static block saves its final HTML into the post content at edit time — fast to serve, but the markup is frozen at save (change the template and old posts keep the old HTML). A dynamic block saves only its attributes and generates the HTML on each render via a PHP callback — so the output always reflects the current template and current data. For a gallery, dynamic is almost always right: you want the gallery to reflect the current images, current responsive sizes, and current layout logic, not HTML frozen whenever the editor last saved. You make a block dynamic by setting "render": "file:./render.php" in block.json (the modern way) and providing that render file:
<?php
// render.php — receives $attributes, $content, $block
$ids = $attributes['ids'] ?? [];
$layout = $attributes['layout'] ?? 'grid';
echo '<div class="myplugin-gallery is-' . esc_attr( $layout ) . '">';
foreach ( $ids as $id ) {
// core function handles srcset/sizes/lazy-loading
echo wp_get_attachment_image( $id, 'large' );
}
echo '</div>';
Note wp_get_attachment_image() doing the responsive-image heavy lifting — that is the WordPress-way point from the pillar. Server-rendering via the PHP callback is also what keeps the gallery SEO- and crawler-friendly: the images are real <img> tags in the HTML the browser (and Googlebot, and AI crawlers) receives, not script-injected after load.
Attributes and the editor UI
The editor side (the editorScript) is where you build the block’s editing experience in React, using WordPress’s component library. For a gallery: MediaUpload / MediaUploadCheck for selecting images, InspectorControls for the settings sidebar (layout picker, columns, gap), and the block’s edit function rendering a preview. The attributes declared in block.json are read and written here via the block’s attributes and setAttributes. The skill is building an editor experience that previews accurately while keeping the saved state minimal (just the attributes, since rendering is dynamic). A common beginner mistake is doing rendering work in the editor that should live in the PHP render callback — keep the editor for editing, the render file for output.
Do this now. Scaffold a dynamic gallery block to learn the current model: run npx @wordpress/create-block myplugin-gallery --variant dynamic, which generates the block.json, editor script, and render.php wired correctly. Then replace the render callback’s output with a wp_get_attachment_image() loop over an ids attribute. You will have a working, server-rendered, responsive gallery block in minutes, and the scaffold teaches you the 2026 structure better than any older tutorial.
Frontend interactivity: the Interactivity API
A gallery needs frontend behaviour — opening a lightbox, filtering, navigating — and in 2026 the standard way to add it is the Interactivity API, not bolting jQuery or a separate React bundle onto the front end. The Interactivity API lets you add directives (declarative attributes) to your server-rendered markup that wire up behaviour, keeping the frontend lightweight (no shipping React to visitors) and integrating with WordPress’s own system. WordPress 6.9 extended it with nested router regions and dynamic overlays — directly useful for gallery modals, where a lightbox is an overlay you route into and out of. For a gallery lightbox, this means you can build the interaction declaratively on top of the server-rendered images rather than hydrating a React app, which is better for performance (INP especially) and aligns with where WordPress is heading.
Block Bindings and other 6.9 APIs
Two more current APIs worth a gallery developer’s attention. The Block Bindings API (extended in 6.9 to bind the Image block’s caption, among other things) lets block attributes be bound to dynamic sources — useful if you want gallery captions pulled from a custom field rather than typed inline. The Abilities API (PHP in 6.9, JS via @wordpress/abilities in 7.0) is the forward-looking one: it lets a plugin declare capabilities that AI and MCP agents can discover and invoke — for a gallery block, you could expose an ability to create or populate a gallery programmatically, which an AI agent could then use. It is early, but building with an awareness of it means your block is positioned for the AI-agent integration that is becoming a real surface in the WordPress world.
Server-side rendering for SEO
Worth restating as its own point because it is where many custom gallery blocks fail: the render callback must produce real, server-side HTML with real <img> tags. A block that renders its images via client-side JavaScript (fetching and injecting them after load) produces a gallery that is invisible to search engines and AI crawlers that do not run JavaScript well — the SEO and AI-citation problem covered across our image SEO work. The dynamic-block PHP render callback is exactly the mechanism that gets this right: the HTML exists before any JavaScript runs. If you take one rule from this article, it is that the gallery’s images must be in the server-rendered output, which the render callback guarantees and a client-only approach does not.
Performance in the block
A custom gallery block is a place it is easy to accidentally ship a lot of JavaScript. Keep the frontend lean: use the Interactivity API rather than a front-end React bundle, lean on wp_get_attachment_image() for correct srcset/sizes rather than hand-built tags, eager-load the first row and lazy-load the rest, and avoid heavy animation libraries for hover/lightbox effects (CSS does most of it). The performance budget from the pillar applies directly here — a block is where you either honour or blow that budget, and a gallery block that ships a megabyte of JS to every visitor is the common failure. Build the block lean and every page using it inherits the leanness.
This is a fair amount of careful work to get right per project, which is why a maintained gallery plugin’s block is often the pragmatic choice over a bespoke one — it has already solved the dynamic-render, Interactivity-API, responsive-image, and performance pieces. FotoGrids ships a Gutenberg block (and Elementor/Divi/Bricks equivalents) built on these patterns, server-rendered with the core image functions, so you get a correct gallery block without rebuilding the model yourself. Building your own is the right call when you have bespoke needs; using a well-built one is the right call when you do not, and knowing the model (from this article) lets you judge which.
Key takeaways
block.jsonis the source of truth and auto-enqueues assets; scaffold with@wordpress/create-block.- Make a gallery dynamic (
"render": "file:./render.php") so output reflects current data and is server-rendered for SEO. - Use the Interactivity API (not front-end React) for lightboxes and filtering; 6.9 added overlay/router support.
- The Abilities API (6.9/7.0) exposes block capabilities to AI/MCP agents — design with awareness of it.
What makes a gallery a ‘dynamic’ Gutenberg block?
A dynamic block saves only its attributes (which images, what layout) and generates the HTML on each render through a PHP callback, set via "render": "file:./render.php" in block.json. This means the output always reflects current data and template logic, and it is server-rendered — so the images are real HTML tags visible to search engines, unlike a client-side-rendered gallery.
How should a gallery block add a lightbox in 2026?
Through the WordPress Interactivity API, which adds frontend behaviour declaratively on top of server-rendered markup without shipping React to the front end — better for performance, especially INP. WordPress 6.9 added nested router regions and dynamic overlays well suited to lightbox modals. Avoid bolting on jQuery or a separate React bundle.
Why must a gallery block be server-rendered?
Because search engines and AI crawlers often do not run JavaScript well, so a gallery whose images are injected client-side after load can be invisible to them. A dynamic block’s PHP render callback produces real img tags in the HTML before any JavaScript runs, keeping the gallery crawlable and SEO-friendly. This is the single most important rule for a custom gallery block.
What is the fastest way to start a custom gallery block?
Run npx @wordpress/create-block myplugin-gallery --variant dynamic. It scaffolds the block.json, editor script, and render.php wired correctly for a dynamic block, teaching you the current 2026 structure. Then replace the render callback’s output with a wp_get_attachment_image() loop over an ids attribute for a working, responsive, server-rendered gallery.