Ein dynamischer Gutenberg-Block mit block.json und render.php

Inhalt
- Was das create-block-Gerüst heute erzeugt
- block.json als Vertrag zwischen Editor und Server
- Registrierung über ein Manifest statt einzelner block.json-Dateien
- render.php und seine drei Variablen
- Die Editorseite: edit.js mit ServerSideRender
- Statisch oder dynamisch: was in der Datenbank landet
- Grenzen und offene Punkte
- Fragen und Antworten
- Quellen
Ein Block, dessen Ausgabe von Daten abhängt, die sich nach dem Speichern des Beitrags ändern (die neuesten Beiträge, ein Preis, eine Terminliste), kann sein fertiges HTML nicht in post_content ablegen. Jeder neue Beitrag in der Rubrik ließe das gespeicherte Markup veralten. Statische Blocks haben noch eine zweite Schwäche: Ändert sich das Markup der save-Funktion, passen ältere Beiträge nicht mehr dazu, der Editor meldet ungültigen Inhalt, und der Block braucht einen Deprecation-Eintrag. Ein dynamischer Block umgeht beides. Der Editor speichert nur den Block-Kommentar mit den Attributen, das Markup erzeugt PHP bei jedem Aufruf.
Dieser Beitrag baut einen solchen Block von Grund auf: eine Liste der letzten Beiträge einer Rubrik mit den Attributen categoryId und count. Er zeigt, was @wordpress/create-block mit Stand 29.09.2026 erzeugt, wie block.json Editor und Server verbindet, warum das Gerüst Blocks inzwischen über ein generiertes Manifest registriert und welche Variablen render.php erhält. Der Code ist vollständig; das Plugin läuft ab WordPress 6.9, aktuell ist zum Zeitpunkt des Schreibens Version 7.1.2.
Was das create-block-Gerüst heute erzeugt
Das offizielle Werkzeug ist das npm-Paket @wordpress/create-block; am Abrufdatum ist Version 4.99.0 vom 23.09.2026 die neueste veröffentlichte. Vorausgesetzt wird Node.js ab 20.10.0. Die Standardvorlage kennt zwei Varianten, static und dynamic. Die dynamische Variante unterscheidet sich an genau drei Stellen: block.json erhält ein Feld render mit Verweis auf render.php, index.js registriert den Block ohne save-Funktion, und die npm-Skripte kopieren PHP-Dateien in den Build-Ordner. Für das Beispiel sehen die Befehle so aus:
npx @wordpress/create-block@latest category-posts --variant dynamic --namespace lw --title "Category Posts"
cd category-posts
npm install @wordpress/components @wordpress/core-data @wordpress/data @wordpress/html-entities @wordpress/server-side-render --save
npm startDie zusätzliche Zeile mit npm install ist für den Build nicht zwingend, denn @wordpress/scripts behandelt Importe aus @wordpress/* als externe Abhängigkeiten und bildet sie auf die Skript-Handles ab, die WordPress ohnehin mitbringt. Mit installierten Paketen kennen Editor-Werkzeuge und Linter die Importe aber. npm start beobachtet src/ und schreibt nach build/, npm run build erzeugt die minimierte Fassung für die Veröffentlichung. Der Plugin-Ordner sieht danach etwa so aus:
category-posts/ (abridged)
├── category-posts.php plugin header and block registration
├── package.json build/start scripts with --blocks-manifest
├── readme.txt
├── src/
│ └── category-posts/
│ ├── block.json
│ ├── index.js
│ ├── edit.js
│ ├── render.php
│ ├── view.js not needed here, deleted
│ ├── style.scss
│ └── editor.scss
└── build/ written by npm start / npm run build
├── blocks-manifest.php
└── category-posts/
├── block.json
├── index.js
├── index.asset.php
├── index.css
├── style-index.css
└── render.phpMit der Standardvorlage liegt der Block in src/<slug>/, nicht direkt in src/. Die Text Domain entspricht standardmäßig dem Slug, hier category-posts. Zusätzlich legt das Gerüst eine view.js samt Eintrag viewScript für JavaScript im Frontend an. Eine serverseitig gerenderte Liste ohne Interaktion braucht das nicht, daher entfernt das Beispiel beides. Die beiden npm-Skripte, auf die es bei einem dynamischen Block ankommt, erzeugt das Gerüst so:
{
"scripts": {
"build": "wp-scripts build --webpack-copy-php --blocks-manifest",
"start": "wp-scripts start --webpack-copy-php --blocks-manifest"
}
}--webpack-copy-php kopiert alle PHP-Dateien aus src nach build; ohne den Schalter übernimmt @wordpress/scripts nur PHP-Dateien, die eine block.json unter render oder variations nennt. --blocks-manifest schreibt build/blocks-manifest.php, dazu mehr in den nächsten Abschnitten. Der erzeugte Plugin-Header nennt „Requires at least: 6.8“, die readme.txt „Tested up to: 6.8“. Beides sind Vorgaben der Vorlage, keine Aussage über die aktuelle Version, und sollte vor einer Veröffentlichung angepasst werden. Die Header-Felder behandelt ein eigener Beitrag dieser Reihe zu readme.txt, Plugin-Header und SVN.
block.json als Vertrag zwischen Editor und Server
block.json wird auf beiden Seiten gelesen. PHP registriert daraus Blocktyp, Attribute und Assets; das Editor-Bundle importiert dieselbe Datei, damit JavaScript Namen und Attributschema kennt. Im Beispiel hat die Datei in src/category-posts/ diesen Inhalt:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "lw/category-posts",
"version": "0.1.0",
"title": "Category Posts",
"category": "widgets",
"icon": "list-view",
"description": "Lists the latest posts of one category, rendered on the server.",
"attributes": {
"categoryId": {
"type": "integer",
"default": 0
},
"count": {
"type": "integer",
"default": 5
}
},
"usesContext": [ "postId" ],
"supports": {
"html": false,
"align": [ "wide", "full" ],
"color": {
"background": true,
"text": true,
"link": true
},
"spacing": {
"margin": true,
"padding": true
},
"typography": {
"fontSize": true
}
},
"textdomain": "category-posts",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php"
}apiVersion 3 ist die aktuelle Version der Block-API und kam mit WordPress 6.3. Fehlt das Feld, nimmt WordPress Version 1 an. Version 3 hängt am Editor im iframe: Seit 6.3 nutzte der Beitragseditor ein iframe nur, wenn alle registrierten Blocks Version 3 oder höher angaben. WordPress 7.0 stellte die Prüfung auf die Blocks um, die tatsächlich im Beitragsinhalt stehen, und laut Dokumentation der Block-API nutzt WordPress 7.1 für den Beitragseditor immer ein iframe, unabhängig von apiVersion. Für einen neuen Block bleibt davon eine Folge: Editor-Styles müssen im iframe greifen, was sie tun, wenn sie über editorStyle und style in block.json geladen werden.
Beide Attribute haben den Typ integer und einen Standardwert. Vor dem Rendern prüft WordPress jedes Attribut gegen sein Schema, verwirft ungültige Werte und setzt für fehlende Attribute den Standardwert ein. usesContext fordert den Kontext postId an, den render_block() aus dem globalen Beitrag befüllt. So kann der Block den Beitrag auslassen, in dem er selbst steht.
Der Abschnitt supports schaltet Ausrichtung, Farben, Abstände und Schriftgröße frei. Das Markup dafür erzeugen später get_block_wrapper_attributes() und useBlockProps(), sodass kein eigener Code nötig ist. editorScript und editorStyle werden nur im Editor geladen, style im Editor und im Frontend. render ist ein reines Serverfeld.
Registrierung über ein Manifest statt einzelner block.json-Dateien
Lange war register_block_type( __DIR__ . '/build' ) oder ein Pfad zum Block-Ordner die übliche Registrierung. Das funktioniert weiterhin und passt für einen einzelnen Block ohne Build-Schritt. Bei jedem Aufruf liest es allerdings eine JSON-Datei von der Platte und dekodiert sie. WordPress 6.7 brachte wp_register_block_metadata_collection(), das ein PHP-Manifest mit den Metadaten mehrerer Blocks registriert; die Dev-Note von Felix Arntz nennt die eingesparten Dateisystem- und JSON-Operationen als Grund. In 6.7 brauchte trotzdem jeder Block noch einen eigenen Aufruf von register_block_type(). WordPress 6.8 schloss die Lücke mit wp_register_block_types_from_metadata_collection(): Ein Aufruf registriert die Sammlung und alle darin enthaltenen Blocktypen.
Das aktuelle Gerüst nutzt genau diese Funktion, sobald @wordpress/scripts eingebunden ist, und das ist die Vorgabe. Die Plugin-Hauptdatei des Beispiels sieht deshalb so aus:
/**
* Plugin Name: Category Posts
* Description: Server-rendered block that lists the latest posts of one category.
* Version: 0.1.0
* Requires at least: 6.9
* Requires PHP: 7.4
* Author: Lukas Wojcik
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: category-posts
*
* @package Lw
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Registers every block listed in build/blocks-manifest.php.
*
* The manifest is generated by wp-scripts (--blocks-manifest), so WordPress
* does not have to read and decode each block.json on every request.
*/
function lw_category_posts_block_init() {
wp_register_block_types_from_metadata_collection(
__DIR__ . '/build',
__DIR__ . '/build/blocks-manifest.php'
);
}
add_action( 'init', 'lw_category_posts_block_init' );Der erste Parameter ist der Basisordner der Sammlung, der zweite das Manifest. Bleibt der zweite leer, muss der Basisordner auf eine zuvor registrierte Sammlung verweisen. Plugins, die noch WordPress 6.7 oder älter unterstützen, brauchen eine Prüfung mit function_exists() und einen Rückfallweg, wie ihn die Dev-Note zu 6.8 beschreibt. Das Beispiel umgeht das mit „Requires at least: 6.9“, was es für den weiter unten gezeigten Import im Editor ohnehin benötigt. Ohne Build-Schritt reicht ein einzelner Aufruf:
// Single block, without a manifest: reads build/category-posts/block.json.
register_block_type( __DIR__ . '/build/category-posts' );Ein Detail wird leicht übersehen: Das Manifest ist ein Build-Ergebnis. Eine in src/ geänderte block.json wirkt erst nach dem nächsten Build, auch auf der Serverseite.
render.php und seine drei Variablen
Enthält block.json ein Feld render, legt WordPress bei der Registrierung einen render_callback an. Im Core-Quelltext ist das eine statische Closure, die die Ausgabepufferung startet, die Datei per require lädt und den Puffer zurückgibt. In der Datei stehen drei Variablen bereit: $attributes (Array der Block-Attribute, Standardwerte bereits eingesetzt), $content (der gespeicherte innere Inhalt, wichtig für Blocks mit inneren Blocks, hier immer leer) und $block (die Instanz von WP_Block mit Namen, Kontext und geparstem Block). Die Render-Datei des Beispiels:
/**
* Server-side rendering of the lw/category-posts block.
*
* Variables provided by WordPress:
*
* @var array $attributes Block attributes, defaults from block.json applied.
* @var string $content Inner content (always empty for this block).
* @var WP_Block $block Block instance: name, context, parsed block.
*
* @package Lw
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
$lw_category_id = isset( $attributes['categoryId'] ) ? absint( $attributes['categoryId'] ) : 0;
$lw_count = isset( $attributes['count'] ) ? absint( $attributes['count'] ) : 5;
$lw_count = min( max( $lw_count, 1 ), 10 );
$lw_term = $lw_category_id ? get_term( $lw_category_id, 'category' ) : null;
// Render nothing until a valid category has been chosen.
if ( ! $lw_term instanceof WP_Term ) {
return;
}
// Do not list the post that contains the block (context from "usesContext").
$lw_current_id = isset( $block->context['postId'] ) ? absint( $block->context['postId'] ) : 0;
$lw_posts = get_posts(
array(
'category' => $lw_term->term_id,
'numberposts' => $lw_count,
'post_status' => 'publish',
'has_password' => false,
'exclude' => $lw_current_id ? array( $lw_current_id ) : array(),
)
);
<div echo wp_kses_data( get_block_wrapper_attributes() ); >
if ( empty( $lw_posts ) ) :
<p> esc_html_e( 'No posts in this category yet.', 'category-posts' ); </p>
else :
<ul class="wp-block-lw-category-posts__list">
foreach ( $lw_posts as $lw_post ) :
<li>
<a href="<?php echo esc_url( get_permalink( $lw_post ) ); ?>"> echo esc_html( get_the_title( $lw_post ) ); </a>
<time datetime="<?php echo esc_attr( get_the_date( 'c', $lw_post ) ); ?>"> echo esc_html( get_the_date( '', $lw_post ) ); </time>
</li>
endforeach;
</ul>
endif;
</div>Mehrere Entscheidungen in dieser Datei folgen daraus, wie sie geladen wird. Da require für jede Block-Instanz auf einer Seite erneut läuft, darf die Datei keine Funktionen oder Klassen deklarieren; eine zweite Instanz des Blocks endete sonst in einem fatalen Fehler. Hilfsfunktionen gehören in die Plugin-Hauptdatei oder in ein eigenes Include. Für PHP_CodeSniffer sehen die Variablen global aus, deshalb tragen sie das Präfix lw_. Die Prüfung stellt das allerdings nicht zufrieden: WPCS lehnt Präfixe mit weniger als vier Zeichen ab, und Plugin Check 2.1.0, das das erwartete Präfix aus dem Plugin-Code ableitet (hier aus dem Funktionsnamen lw_category_posts_block_init), meldet jede dieser Variablen als Warnung. Auf die Funktion des Blocks haben die Warnungen keinen Einfluss. return beendet die Datei vorzeitig und hinterlässt einen leeren Puffer, so rendert ein Block gar nichts.
Attribute sind Daten aus post_content und damit Eingaben. Die Schemaprüfung garantiert ganze Zahlen, aber keinen sinnvollen Bereich, daher begrenzen absint(), min() und max() die Anzahl auf 1 bis 10. get_term() stellt sicher, dass die Rubrik existiert. get_posts() ist eine dünne Hülle um WP_Query, die ignore_sticky_posts, no_found_rows und suppress_filters selbst setzt; das passt zu einer kurzen Liste ohne Seitennavigation. Wie weitere Abfrageargumente wie meta_query zu SQL werden, zeigt der Beitrag zu meta_query in WP_Query. Jede Ausgabe läuft durch die passende Escaping-Funktion: esc_url() für den Link, esc_html() für Titel und Datum, esc_attr() für das Attribut datetime. Die allgemeinen Regeln dazu behandelt ein eigener Beitrag dieser Reihe zu Sanitizing, Escaping, Nonces und Capabilities.
get_block_wrapper_attributes() liefert die Attribut-Zeichenkette für das umschließende Element: die Klasse wp-block-lw-category-posts plus Klassen und Inline-Styles aus den supports-Einstellungen. Die Funktion escapet jeden Wert selbst mit esc_attr(), wie ihr Quelltext zeigt. In der Liste der automatisch escapenden Funktionen der WordPress Coding Standards fehlt sie allerdings, sodass ein nacktes echo den Escaping-Sniff auslöst; Plugin Check 2.1.0 meldet es als Fehler (WordPress.Security.EscapeOutput.OutputNotEscaped). wp_kses_data() lässt eine Attribut-Zeichenkette ohne Tags unverändert und stellt die Prüfung zufrieden; ein begründeter phpcs:ignore-Kommentar ist die Alternative.
Die Editorseite: edit.js mit ServerSideRender
In JavaScript registriert index.js den Block nur mit edit. Fehlt save, setzt die Block-API ein Standard-save ein, das null zurückgibt:
import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import Edit from './edit';
import metadata from './block.json';
// No save(): the default save returns null, so only the block comment
// with its attributes is stored in post_content.
registerBlockType( metadata.name, {
edit: Edit,
} );edit.js liefert die Einstellungen in der Seitenleiste und eine Vorschau. Die Rubriken kommen aus dem Core-Datenspeicher, mit derselben Abfrage, die der Core-Block „Neueste Beiträge“ verwendet. Die Vorschau erzeugt ServerSideRender: Die Komponente schickt die Attribute an die REST-Route /wp/v2/block-renderer/lw/category-posts und fügt das HTML ein, das render.php zurückgibt:
import { __ } from '@wordpress/i18n';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import {
PanelBody,
Placeholder,
RangeControl,
SelectControl,
Spinner,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
import { ServerSideRender } from '@wordpress/server-side-render';
import metadata from './block.json';
import './editor.scss';
// Same query shape as the core Latest Posts block uses for its category list.
const CATEGORIES_QUERY = { per_page: -1, _fields: 'id,name', context: 'view' };
export default function Edit( { attributes, setAttributes } ) {
const { categoryId, count } = attributes;
const blockProps = useBlockProps();
const categories = useSelect(
( select ) =>
select( coreStore ).getEntityRecords(
'taxonomy',
'category',
CATEGORIES_QUERY
),
[]
);
const options = [
{ value: '0', label: __( '— Select a category —', 'category-posts' ) },
...( categories ?? [] ).map( ( term ) => ( {
value: String( term.id ),
label: decodeEntities( term.name ),
} ) ),
];
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'category-posts' ) }>
{ categories ? (
<SelectControl
__next40pxDefaultSize
__nextHasNoMarginBottom
label={ __( 'Category', 'category-posts' ) }
value={ String( categoryId ) }
options={ options }
onChange={ ( value ) =>
setAttributes( { categoryId: Number( value ) } )
}
/>
) : (
<Spinner />
) }
<RangeControl
__next40pxDefaultSize
__nextHasNoMarginBottom
label={ __( 'Number of posts', 'category-posts' ) }
value={ count }
min={ 1 }
max={ 10 }
onChange={ ( value ) =>
setAttributes( { count: value ?? 5 } )
}
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{ categoryId ? (
<ServerSideRender
block={ metadata.name }
attributes={ attributes }
skipBlockSupportAttributes
/>
) : (
<Placeholder
icon="list-view"
label={ __( 'Category Posts', 'category-posts' ) }
instructions={ __(
'Choose a category in the block settings.',
'category-posts'
) }
/>
) }
</div>
</>
);
}In der mit WordPress 6.9 ausgelieferten Paketversion (6.9.5) exportiert @wordpress/server-side-render die Komponente ServerSideRender bereits als benannten Export; der Standard-Export gilt als veraltet und bleibt als Kompatibilitätsschicht erhalten. Dieser benannte Import ist der Grund für „Requires at least: 6.9“ im Plugin-Header. skipBlockSupportAttributes nimmt die Style-Attribute aus der Anfrage, denn das äußere div erhält Farben und Abstände schon über useBlockProps(); sonst erschienen sie doppelt. Die Komponente hängt außerdem die ID des aktuellen Beitrags an die Anfrage, sodass der Kontext postId in render.php auch im Editor greift. Die beiden Props __next… an den Steuerelementen schalten ältere WordPress-Versionen auf die aktuelle Größe und Abstände der Controls; laut Changelog der Components sind beide inzwischen Standard und können entfallen, sobald diese Versionen nicht mehr unterstützt werden.
Das Diagramm fasst zusammen, wer welche Datei wann liest: PHP liest das Manifest bei jedem Aufruf, der Editor lädt das Skript-Bundle und beide Stylesheets, und render.php läuft sowohl für das Frontend als auch für die Editor-Vorschau über die REST-Route.
Statisch oder dynamisch: was in der Datenbank landet
Für das Beispiel steht in der Datenbank nur der Block-Kommentar. Attribute, die ihrem Standardwert entsprechen, werden nicht serialisiert:
<!-- wp:lw/category-posts {"categoryId":7,"count":3} /-->Hier liegt der Kern des Unterschieds. Ein statischer Block speichert die Ausgabe seiner save-Funktion als HTML zwischen den Kommentar-Begrenzern, und der Editor vergleicht dieses HTML beim Öffnen des Beitrags mit einem frischen Aufruf von save. Jede Änderung am Markup (eine zusätzliche Klasse, ein Wrapper, ein anderes Tag) bricht deshalb bestehende Inhalte, solange kein Array deprecated die alten Fassungen beschreibt. Beim dynamischen Block gibt save null zurück, der Editor überspringt die Markup-Prüfung, und eine Änderung an render.php erreicht nach dem nächsten Deployment jedes Vorkommen des Blocks. Das Block-Editor-Handbuch nennt genau diese beiden Fälle für dynamisches Rendern: Inhalte, die sich ändern sollen, ohne dass der Beitrag aktualisiert wird, und Markup, das sich voraussichtlich ändert.
Dynamisches Rendern verursacht zusätzliche Arbeit auf dem Server: Jeder Seitenaufruf mit dem Block führt eine Datenbankabfrage aus, jede Attributänderung im Editor eine REST-Anfrage. Für eine kurze Liste ist das meist vertretbar. Ist die Ausgabe aufwendig zu erzeugen, kann ein Transient oder der Objekt-Cache das gerenderte HTML speichern. Beides gehört nicht zum Beispiel, und für diesen Beitrag wurde nichts gemessen. Außerdem hängt die Ausgabe vom aktiven Plugin ab: Ohne das Plugin rendert der Block nichts, weil post_content kein Ersatz-HTML enthält.
Grenzen und offene Punkte
- Die Dokumentation von
ServerSideRenderbezeichnet die Komponente als Rückfall- oder Altmechanismus, der für die Entwicklung neuer Funktionen nicht geeignet ist. Für Blocks, deren Vorschau sich in JavaScript aus REST-Daten bauen lässt, gilt clientseitiges Rendern als empfohlener Weg. Das Beispiel nutzt die Komponente bewusst, damitrender.phpdie einzige Quelle des Markups bleibt; der Preis ist ein verschachtelter Wrapper im Editor (das äußeredivaususeBlockProps()enthält dasdivausrender.php). - WordPress 7.0 hat die Registrierung von Blocks allein in PHP eingeführt: Mit
'autoRegister' => trueuntersupportsund einemrender_callbackerscheint ein Block ohne eigenes JavaScript im Editor, und für Attribute vom Typ string, integer, boolean und enum entstehen Steuerelemente automatisch. Eine Rubrikenauswahl mit Namen statt numerischer IDs deckt das nicht ab, deshalb behält das Beispiel eine eigeneedit.js. - Das beschriebene Gerüst entspricht
@wordpress/create-block4.99.0. Das Paket erscheint in kurzen Abständen; Dateinamen, Schalter und Vorgaben können sich mit späteren Versionen ändern. - Plugin Check 2.1.0 meldet für das Beispiel unter WordPress 7.1.2 keine Fehler (geprüft am 30.09.2026). Für jede Kopie von
render.phpinsrc/undbuild/listet es siebenPrefixAllGlobals-Warnungen und eine Warnung zum Parameterexcludevonget_posts(); ein nacktesecho get_block_wrapper_attributes()meldet es als Fehler. - Caching der gerenderten Liste und die Kosten der REST-Vorschau wurden nicht gemessen.
Fragen und Antworten
Warum wirkt eine Änderung an block.json nicht, obwohl die Datei gespeichert ist?
Registriert wird aus build/blocks-manifest.php, nicht aus src/. Das Manifest entsteht beim Build; erst nach npm run build oder bei laufendem npm start sieht auch PHP die neue Fassung.
Darf render.php eigene Hilfsfunktionen deklarieren?
Nein. WordPress lädt die Datei per require für jede Block-Instanz erneut. Steht der Block zweimal auf einer Seite, führt eine zweite Deklaration derselben Funktion zu einem fatalen Fehler. Hilfsfunktionen gehören mit Präfix in die Plugin-Hauptdatei oder in ein eigenes Include.
Braucht ein serverseitig gerenderter Block seit WordPress 7.0 überhaupt noch JavaScript?
Nicht zwingend. Mit 'autoRegister' => true unter supports und einem render_callback erscheint ein Block ohne eigenes JavaScript im Editor; Steuerelemente entstehen für Attribute vom Typ string, integer, boolean und enum. Für eine Auswahlliste mit Rubriknamen oder eigene Platzhalter bleibt eine edit.js nötig.
Quellen
- @wordpress/create-block – Block Editor Handbook (abgerufen am 29.09.2026)
- Quelltext von create-block: templates.js, init-package-json.js, Plugin- und Block-Vorlagen (GitHub) (abgerufen am 29.09.2026)
- @wordpress/create-block auf npm, Version 4.99.0 (abgerufen am 29.09.2026)
- Tutorial: Build your first block (abgerufen am 29.09.2026)
- Metadata in block.json (abgerufen am 29.09.2026)
- Block API Versions (abgerufen am 29.09.2026)
- Static or Dynamic rendering of a block (abgerufen am 29.09.2026)
- Registration of a block (abgerufen am 29.09.2026)
- Deprecation (abgerufen am 29.09.2026)
- @wordpress/scripts (abgerufen am 29.09.2026)
- Dependency Extraction Webpack Plugin (README) (abgerufen am 29.09.2026)
- @wordpress/server-side-render und Changelog des Pakets (abgerufen am 29.09.2026)
- get_block_wrapper_attributes() (abgerufen am 29.09.2026)
- wp_register_block_types_from_metadata_collection() (abgerufen am 29.09.2026)
- New block type registration APIs to improve performance in WordPress 6.7 (abgerufen am 29.09.2026)
- More efficient block type registration in 6.8 (abgerufen am 29.09.2026)
- PHP-only block registration (abgerufen am 29.09.2026)
- wordpress-develop, Branch 7.1: blocks.php und post.php (get_posts) (abgerufen am 29.09.2026)
- WordPress Coding Standards: EscapingFunctionsTrait.php (abgerufen am 29.09.2026)
- WordPress-Versionsabfrage (aktuelle Version 7.1.2) (abgerufen am 29.09.2026)