Der Gutenberg-Editor hat WordPress grundlegend verändert. Redakteure arbeiten visuell, Inhalte entstehen blockweise — und das funktioniert gut, solange der eingebaute Block-Vorrat das abbildet, was das Projekt tatsächlich braucht. Irgendwann tut er das nicht mehr.
Wer dann mit dem Absatz-Block keine CI-konforme Infobox bauen kann, löst das oft mit einem Plugin. Das Plugin bringt zwanzig Blöcke, von denen drei gebraucht werden, und aktualisiert sich beim nächsten WordPress-Major gerne auf eine Version, die irgendetwas bricht. Eigene Blöcke sind das Gegenprogramm: einmal gebaut, exakt auf das Projekt zugeschnitten, vollständig unter Kontrolle des Entwicklungsteams.
Custom Blocks sind redaktionelles Interface-Design. Sie übersetzen Gestaltung, Marke und Governance in konkrete Bedienbarkeit. Ein Custom Block lässt nur zu, was vorgesehen ist — kein freigestaltetes Markup, keine Farbabweichungen vom Brand, keine „ich hab den Block kurz umgebaut"-Situationen.
Es gibt zwei verbreitete Wege, eigene Blöcke in WordPress zu bauen.
Der erste ist der PHP-zentrierte Weg mit ACF: Advanced Custom Fields Pro erlaubt es, Blöcke als PHP-Templates zu definieren. Felder werden im ACF-Interface gepflegt, das Template rendert sie im Frontend. Das kommt ohne React-Build-Pipeline aus und ist gut für Teams, die tief in PHP zuhause sind, aber keinen JavaScript-Build-Prozess aufsetzen wollen.
Der zweite ist die native WordPress Block API: Blöcke entstehen als React-Komponenten, werden über block.json registriert und mit dem offiziellen @wordpress/create-block-Scaffold aufgesetzt. Das ist aufwendiger im Setup, aber unabhängig von Drittanbietern, direkt in Core-Konzepte integriert und auf Dauer das stabilere Fundament.
Dieser Artikel folgt dem nativen Weg.
Das Beispiel baut eine Infobox — ein klassischer Use-Case: eine Überschrift, ein Fließtext, visuell vom normalen Absatz abgesetzt.
Mit @wordpress/create-block entsteht ein vollständig aufgesetzter Block als Plugin-Ordner:
npx @wordpress/create-block@latest infobox --namespace mein-projekt
cd infobox
npm start
Der --namespace-Parameter setzt den eindeutigen Bezeichner des Blocks. Empfehlenswert ist der Projekt- oder Agenturname, um Konflikte mit anderen Plugins zu vermeiden. Der Block heißt dadurch intern mein-projekt/infobox — und aus dieser Kombination leitet WordPress später auch die automatisch generierte CSS-Klasse ab.
Nach npm start läuft ein Watcher, der Änderungen an den Quelldateien automatisch kompiliert. Der Ordner hat folgende Struktur:
infobox/
├── src/
│ ├── block.json ← Metadaten und Attribute
│ ├── index.js ← Registrierung
│ ├── edit.js ← Editor-Ansicht (React)
│ ├── save.js ← Frontend-Output (React)
│ ├── editor.scss ← Editor-spezifische Styles
│ └── style.scss ← Frontend-Styles (auch im Editor geladen)
├── build/ ← Kompiliertes Bundle (nicht manuell bearbeiten)
├── infobox.php ← Plugin-Einstiegspunkt
└── package.json
block.json ist das zentrale Konfigurationsfile. Hier werden Metadaten, Attribute und Assets des Blocks beschrieben:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "mein-projekt/infobox",
"version": "0.1.0",
"title": "Infobox",
"category": "text",
"description": "Hervorgehobene Infobox mit Überschrift und Fließtext.",
"supports": {
"html": false
},
"attributes": {
"heading": {
"type": "string",
"source": "html",
"selector": "h3",
"default": ""
},
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
}
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
„supports“ { „html“: false } verhindert, dass Redakteure in den HTML-Modus wechseln und das Block-Markup manuell überschreiben können; eine einfache, aber wirkungsvolle Absicherung.
Block Supports lassen sich weit ausbauen: Hintergrundfarben, Typografie, Abstände. Wer sie aktiviert, sollte sie aber über theme.json auf eine definierte Palette begrenzen, sonst öffnet der Block genau die Tür, die der Artikel vorher schließen will.
Die attributes beschreiben alle Felder, die der Block speichert. Über source und selector legt WordPress fest, aus welchem Teil des gespeicherten HTMLs die Werte beim erneuten Laden gelesen werden.
edit.js definiert, was Redakteure im Gutenberg-Editor sehen und bearbeiten. Die Funktion bekommt attributes (aktueller Zustand) und setAttributes (Setter) übergeben:
import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit({ attributes, setAttributes }) {
const { heading, content } = attributes;
// useBlockProps hängt automatisch die kanonische Block-Klasse
// (.wp-block-mein-projekt-infobox) sowie Gutenberg-Accessibility-Attribute an
const blockProps = useBlockProps();
return (
<>
{/* InspectorControls erscheint in der rechten Sidebar des Editors */}
<InspectorControls>
<PanelBody title={ __('Infobox-Einstellungen', 'infobox') }>
<p>Hier lassen sich Varianten, Farben oder Icons ergänzen.</p>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<RichText
tagName="h3"
value={ heading }
onChange={ (val) => setAttributes({ heading: val }) }
placeholder={ __('Überschrift …', 'infobox') }
/>
<RichText
tagName="p"
value={ content }
onChange={ (val) => setAttributes({ content: val }) }
placeholder={ __('Inhalt …', 'infobox') }
/>
</div>
</>
);
}
useBlockProps() ergänzt das Root-Element automatisch um die block-spezifische CSS-Klasse — bei name: „mein-projekt/infobox“ ist das .wp-block-mein-projekt-infobox. Zusätzliche Klassen lassen sich als Parameter übergeben, die kanonische Klasse entsteht aber immer aus Namespace und Blockname.
RichText rendert kein separates Input-Feld, Redakteure schreiben direkt im Block. Das Ergebnis fühlt sich an wie nativer Gutenberg-Inhalt, weil es das ist.
save.js beschreibt das finale HTML, das in der Datenbank gespeichert und im Frontend ausgegeben wird. Kein State, keine Event-Handler, eine reine Render-Funktion:
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Save({ attributes }) {
const { heading, content } = attributes;
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<RichText.Content tagName="h3" value={ heading } />
<RichText.Content tagName="p" value={ content } />
</div>
);
}
Das Ergebnis ist statisches HTML — kein zusätzliches JavaScript im Frontend, kein unnötiger Runtime-Overhead.
Wichtig: Die Struktur von save.js darf nach dem ersten Produktiveinsatz nicht mehr verändert werden, ohne eine Block Deprecation zu definieren. WordPress prüft beim Laden jedes gespeicherten Blocks, ob das hinterlegte HTML mit der aktuellen save()-Funktion übereinstimmt. Abweichungen führen zum „Block ist ungültig"-Fehler im Editor.
Der erzeugte Ordner ist bereits ein vollständiges WordPress-Plugin. Er wird in wp-content/plugins/ abgelegt und im Backend aktiviert. Der Einstiegspunkt infobox.php registriert den Block über:
function mein_projekt_infobox_block_init() {
register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'mein_projekt_infobox_block_init' );
register_block_type liest alle Informationen direkt aus build/block.json: Attribute, Assets, Editor-Script. Manuelle wp_register_script()-Aufrufe sind nicht nötig.
Für den Produktionsbuild:
npm run build
Das erzeugt optimierte, minifizierte Dateien im build/-Ordner.
style.scss wird sowohl im Editor als auch im Frontend geladen, ideal für die grundlegenden Block-Styles. Die Klasse entspricht dem automatisch generierten Muster aus Namespace und Blockname:
.wp-block-mein-projekt-infobox {
background: #f0f4f8;
border-left: 4px solid #005fa3;
padding: 1.25rem 1.5rem;
border-radius: 4px;
h3 {
margin: 0 0 0.5rem;
font-size: 1rem;
font-weight: 600;
}
p {
margin: 0;
color: #333;
}
}
editor.scss ist für reine Editor-Overrides reserviert — z.B. wenn ein Block im Frontend keinen Rahmen haben soll, im Editor aber visuell klarer abgegrenzt sein soll.
Dieser Block ist funktional, aber ausbaufähig. Typische nächste Schritte: Farbvarianten über ein variant-Attribut („info“, „warning“, „success“), Icons über @wordpress/icons, BlockControls für Alignment-Optionen in der Block-Toolbar oder InnerBlocks, wenn der Block selbst weitere Blöcke aufnehmen soll.
Die vollständige Referenz liegt unter developer.wordpress.org/block-editor — versioniert, nah an Core und zuverlässiger als jedes Tutorial.
Dass WordPress diesen Ansatz im Editor konsequent verfolgt, überrascht nicht. Das Prinzip strukturierter Inhaltselemente ist älter als Gutenberg. In unserem eigenen CMS Atomic gibt es sie von Anfang an. Damals hießen sie Content-Container, heute heißen sie Blöcke. Der Vorteil ist derselbe: Redakteure bauen Seiten aus definierten Bausteinen, konsistent und responsiv, ohne Markup anfassen zu müssen.
Ein Custom Block ist am Ende mehr als ein React-Komponenten-Paar, das in WordPress lebt. Er ist eine Entscheidung darüber, welche Freiheit Redakteure brauchen — und welche Freiheit ein Projekt besser gar nicht erst anbietet. Wer edit.js, save.js und block.json verstanden hat, baut nicht nur Gutenberg-Erweiterungen. Er gestaltet redaktionelle Systeme.