{"id":20867,"date":"2026-10-21T11:47:00","date_gmt":"2026-10-21T09:47:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=20867"},"modified":"2026-10-09T22:33:53","modified_gmt":"2026-10-09T20:33:53","slug":"dynamischer-block-block-json","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/","title":{"rendered":"Ein dynamischer Gutenberg-Block mit block.json und render.php"},"content":{"rendered":"<p>Ein Block, dessen Ausgabe von Daten abh\u00e4ngt, die sich nach dem Speichern des Beitrags \u00e4ndern (die neuesten Beitr\u00e4ge, ein Preis, eine Terminliste), kann sein fertiges HTML nicht in <code>post_content<\/code> ablegen. Jeder neue Beitrag in der Rubrik lie\u00dfe das gespeicherte Markup veralten. Statische Blocks haben noch eine zweite Schw\u00e4che: \u00c4ndert sich das Markup der <code>save<\/code>-Funktion, passen \u00e4ltere Beitr\u00e4ge nicht mehr dazu, der Editor meldet ung\u00fcltigen 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.<\/p>\n<p>Dieser Beitrag baut einen solchen Block von Grund auf: eine Liste der letzten Beitr\u00e4ge einer Rubrik mit den Attributen <code>categoryId<\/code> und <code>count<\/code>. Er zeigt, was <code>@wordpress\/create-block<\/code> mit Stand 29.09.2026 erzeugt, wie <code>block.json<\/code> Editor und Server verbindet, warum das Ger\u00fcst Blocks inzwischen \u00fcber ein generiertes Manifest registriert und welche Variablen <code>render.php<\/code> erh\u00e4lt. Der Code ist vollst\u00e4ndig; das Plugin l\u00e4uft ab WordPress 6.9, aktuell ist zum Zeitpunkt des Schreibens Version 7.1.2.<\/p>\n<h2>Was das create-block-Ger\u00fcst heute erzeugt<\/h2>\n<p>Das offizielle Werkzeug ist das npm-Paket <code>@wordpress\/create-block<\/code>; am Abrufdatum ist Version 4.99.0 vom 23.09.2026 die neueste ver\u00f6ffentlichte. Vorausgesetzt wird Node.js ab 20.10.0. Die Standardvorlage kennt zwei Varianten, <code>static<\/code> und <code>dynamic<\/code>. Die dynamische Variante unterscheidet sich an genau drei Stellen: <code>block.json<\/code> erh\u00e4lt ein Feld <code>render<\/code> mit Verweis auf <code>render.php<\/code>, <code>index.js<\/code> registriert den Block ohne <code>save<\/code>-Funktion, und die npm-Skripte kopieren PHP-Dateien in den Build-Ordner. F\u00fcr das Beispiel sehen die Befehle so aus:<\/p>\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"bash\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">Shell<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-bash\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-function\">npx<\/span> @wordpress\/create-block@latest category-posts <span class=\"cbb-attr\">--variant<\/span> dynamic <span class=\"cbb-attr\">--namespace<\/span> lw <span class=\"cbb-attr\">--title<\/span> <span class=\"cbb-string\">\"Category Posts\"<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">cd<\/span> category-posts\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">npm<\/span> install @wordpress\/components @wordpress\/core-data @wordpress\/data @wordpress\/html-entities @wordpress\/server-side-render <span class=\"cbb-attr\">--save<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">npm<\/span> start<\/span><\/code><\/pre><\/figure>\n\n\n<p>Die zus\u00e4tzliche Zeile mit <code>npm install<\/code> ist f\u00fcr den Build nicht zwingend, denn <code>@wordpress\/scripts<\/code> behandelt Importe aus <code>@wordpress\/*<\/code> als externe Abh\u00e4ngigkeiten und bildet sie auf die Skript-Handles ab, die WordPress ohnehin mitbringt. Mit installierten Paketen kennen Editor-Werkzeuge und Linter die Importe aber. <code>npm start<\/code> beobachtet <code>src\/<\/code> und schreibt nach <code>build\/<\/code>, <code>npm run build<\/code> erzeugt die minimierte Fassung f\u00fcr die Ver\u00f6ffentlichung. Der Plugin-Ordner sieht danach etwa so aus:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"plain\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">Text<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-plain\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">category-posts\/                 (abridged)\n<\/span><span class=\"cbb-line\">\u251c\u2500\u2500 category-posts.php          plugin header and block registration\n<\/span><span class=\"cbb-line\">\u251c\u2500\u2500 package.json                build\/start scripts with --blocks-manifest\n<\/span><span class=\"cbb-line\">\u251c\u2500\u2500 readme.txt\n<\/span><span class=\"cbb-line\">\u251c\u2500\u2500 src\/\n<\/span><span class=\"cbb-line\">\u2502   \u2514\u2500\u2500 category-posts\/\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 block.json\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 index.js\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 edit.js\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 render.php\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 view.js             not needed here, deleted\n<\/span><span class=\"cbb-line\">\u2502       \u251c\u2500\u2500 style.scss\n<\/span><span class=\"cbb-line\">\u2502       \u2514\u2500\u2500 editor.scss\n<\/span><span class=\"cbb-line\">\u2514\u2500\u2500 build\/                      written by npm start \/ npm run build\n<\/span><span class=\"cbb-line\">    \u251c\u2500\u2500 blocks-manifest.php\n<\/span><span class=\"cbb-line\">    \u2514\u2500\u2500 category-posts\/\n<\/span><span class=\"cbb-line\">        \u251c\u2500\u2500 block.json\n<\/span><span class=\"cbb-line\">        \u251c\u2500\u2500 index.js\n<\/span><span class=\"cbb-line\">        \u251c\u2500\u2500 index.asset.php\n<\/span><span class=\"cbb-line\">        \u251c\u2500\u2500 index.css\n<\/span><span class=\"cbb-line\">        \u251c\u2500\u2500 style-index.css\n<\/span><span class=\"cbb-line\">        \u2514\u2500\u2500 render.php<\/span><\/code><\/pre><\/figure>\n\n\n<p>Mit der Standardvorlage liegt der Block in <code>src\/&lt;slug&gt;\/<\/code>, nicht direkt in <code>src\/<\/code>. Die Text Domain entspricht standardm\u00e4\u00dfig dem Slug, hier <code>category-posts<\/code>. Zus\u00e4tzlich legt das Ger\u00fcst eine <code>view.js<\/code> samt Eintrag <code>viewScript<\/code> f\u00fcr 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\u00fcst so:<\/p>\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"json\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JSON<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-json\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">{\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"scripts\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"build\"<\/span>: <span class=\"cbb-string\">\"wp-scripts build --webpack-copy-php --blocks-manifest\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"start\"<\/span>: <span class=\"cbb-string\">\"wp-scripts start --webpack-copy-php --blocks-manifest\"<\/span>\n<\/span><span class=\"cbb-line\">\t}\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n<p><code>--webpack-copy-php<\/code> kopiert alle PHP-Dateien aus <code>src<\/code> nach <code>build<\/code>; ohne den Schalter \u00fcbernimmt <code>@wordpress\/scripts<\/code> nur PHP-Dateien, die eine <code>block.json<\/code> unter <code>render<\/code> oder <code>variations<\/code> nennt. <code>--blocks-manifest<\/code> schreibt <code>build\/blocks-manifest.php<\/code>, dazu mehr in den n\u00e4chsten Abschnitten. Der erzeugte Plugin-Header nennt \u201eRequires at least: 6.8\u201c, die <code>readme.txt<\/code> \u201eTested up to: 6.8\u201c. Beides sind Vorgaben der Vorlage, keine Aussage \u00fcber die aktuelle Version, und sollte vor einer Ver\u00f6ffentlichung angepasst werden. Die Header-Felder behandelt <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/readme-plugin-header-und-svn\/\">ein eigener Beitrag dieser Reihe zu <code>readme.txt<\/code>, Plugin-Header und SVN<\/a>.<\/p>\n<h2>block.json als Vertrag zwischen Editor und Server<\/h2>\n<p><code>block.json<\/code> 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 <code>src\/category-posts\/<\/code> diesen Inhalt:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"json\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JSON<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-json\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">{\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"$schema\"<\/span>: <span class=\"cbb-string\">\"https:\/\/schemas.wp.org\/trunk\/block.json\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"apiVersion\"<\/span>: <span class=\"cbb-number\">3<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"name\"<\/span>: <span class=\"cbb-string\">\"lw\/category-posts\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"version\"<\/span>: <span class=\"cbb-string\">\"0.1.0\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"title\"<\/span>: <span class=\"cbb-string\">\"Category Posts\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"category\"<\/span>: <span class=\"cbb-string\">\"widgets\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"icon\"<\/span>: <span class=\"cbb-string\">\"list-view\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"description\"<\/span>: <span class=\"cbb-string\">\"Lists the latest posts of one category, rendered on the server.\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"attributes\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"categoryId\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"type\"<\/span>: <span class=\"cbb-string\">\"integer\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"default\"<\/span>: <span class=\"cbb-number\">0<\/span>\n<\/span><span class=\"cbb-line\">\t\t},\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"count\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"type\"<\/span>: <span class=\"cbb-string\">\"integer\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"default\"<\/span>: <span class=\"cbb-number\">5<\/span>\n<\/span><span class=\"cbb-line\">\t\t}\n<\/span><span class=\"cbb-line\">\t},\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"usesContext\"<\/span>: &#91; <span class=\"cbb-string\">\"postId\"<\/span> &#93;,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"supports\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"html\"<\/span>: <span class=\"cbb-constant\">false<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"align\"<\/span>: &#91; <span class=\"cbb-string\">\"wide\"<\/span>, <span class=\"cbb-string\">\"full\"<\/span> &#93;,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"color\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"background\"<\/span>: <span class=\"cbb-constant\">true<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"text\"<\/span>: <span class=\"cbb-constant\">true<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"link\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t\t},\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"spacing\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"margin\"<\/span>: <span class=\"cbb-constant\">true<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"padding\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t\t},\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"typography\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"fontSize\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t\t}\n<\/span><span class=\"cbb-line\">\t},\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"textdomain\"<\/span>: <span class=\"cbb-string\">\"category-posts\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"editorScript\"<\/span>: <span class=\"cbb-string\">\"file:.\/index.js\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"editorStyle\"<\/span>: <span class=\"cbb-string\">\"file:.\/index.css\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"style\"<\/span>: <span class=\"cbb-string\">\"file:.\/style-index.css\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"render\"<\/span>: <span class=\"cbb-string\">\"file:.\/render.php\"<\/span>\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n<p><code>apiVersion<\/code> 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\u00e4ngt am Editor im iframe: Seit 6.3 nutzte der Beitragseditor ein iframe nur, wenn alle registrierten Blocks Version 3 oder h\u00f6her angaben. WordPress 7.0 stellte die Pr\u00fcfung auf die Blocks um, die tats\u00e4chlich im Beitragsinhalt stehen, und laut Dokumentation der Block-API nutzt WordPress 7.1 f\u00fcr den Beitragseditor immer ein iframe, unabh\u00e4ngig von <code>apiVersion<\/code>. F\u00fcr einen neuen Block bleibt davon eine Folge: Editor-Styles m\u00fcssen im iframe greifen, was sie tun, wenn sie \u00fcber <code>editorStyle<\/code> und <code>style<\/code> in <code>block.json<\/code> geladen werden.<\/p>\n<p>Beide Attribute haben den Typ <code>integer<\/code> und einen Standardwert. Vor dem Rendern pr\u00fcft WordPress jedes Attribut gegen sein Schema, verwirft ung\u00fcltige Werte und setzt f\u00fcr fehlende Attribute den Standardwert ein. <code>usesContext<\/code> fordert den Kontext <code>postId<\/code> an, den <code>render_block()<\/code> aus dem globalen Beitrag bef\u00fcllt. So kann der Block den Beitrag auslassen, in dem er selbst steht.<\/p>\n<p>Der Abschnitt <code>supports<\/code> schaltet Ausrichtung, Farben, Abst\u00e4nde und Schriftgr\u00f6\u00dfe frei. Das Markup daf\u00fcr erzeugen sp\u00e4ter <code>get_block_wrapper_attributes()<\/code> und <code>useBlockProps()<\/code>, sodass kein eigener Code n\u00f6tig ist. <code>editorScript<\/code> und <code>editorStyle<\/code> werden nur im Editor geladen, <code>style<\/code> im Editor und im Frontend. <code>render<\/code> ist ein reines Serverfeld.<\/p>\n<h2>Registrierung \u00fcber ein Manifest statt einzelner block.json-Dateien<\/h2>\n<p>Lange war <code>register_block_type( __DIR__ . '\/build' )<\/code> oder ein Pfad zum Block-Ordner die \u00fcbliche Registrierung. Das funktioniert weiterhin und passt f\u00fcr 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 <code>wp_register_block_metadata_collection()<\/code>, 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 <code>register_block_type()<\/code>. WordPress 6.8 schloss die L\u00fccke mit <code>wp_register_block_types_from_metadata_collection()<\/code>: Ein Aufruf registriert die Sammlung und alle darin enthaltenen Blocktypen.<\/p>\n<p>Das aktuelle Ger\u00fcst nutzt genau diese Funktion, sobald <code>@wordpress\/scripts<\/code> eingebunden ist, und das ist die Vorgabe. Die Plugin-Hauptdatei des Beispiels sieht deshalb so aus:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-meta\">&lt;?php<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Plugin Name:       Category Posts<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Description:       Server-rendered block that lists the latest posts of one category.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Version:           0.1.0<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Requires at least: 6.9<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Requires PHP:      7.4<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Author:            Lukas Wojcik<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * License:           GPL-2.0-or-later<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * License URI:       https:\/\/www.gnu.org\/licenses\/gpl-2.0.html<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Text Domain:       category-posts<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @package Lw<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">if<\/span> ( ! <span class=\"cbb-function\">defined<\/span>( <span class=\"cbb-string\">'ABSPATH'<\/span> ) ) {\n<\/span><span class=\"cbb-line\">\texit; <span class=\"cbb-comment\">\/\/ Exit if accessed directly.<\/span>\n<\/span><span class=\"cbb-line\">}\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Registers every block listed in build\/blocks-manifest.php.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * The manifest is generated by wp-scripts (--blocks-manifest), so WordPress<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * does not have to read and decode each block.json on every request.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">lw_category_posts_block_init<\/span>() {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-function\">wp_register_block_types_from_metadata_collection<\/span>(\n<\/span><span class=\"cbb-line\">\t\t__DIR__ . <span class=\"cbb-string\">'\/build'<\/span>,\n<\/span><span class=\"cbb-line\">\t\t__DIR__ . <span class=\"cbb-string\">'\/build\/blocks-manifest.php'<\/span>\n<\/span><span class=\"cbb-line\">\t);\n<\/span><span class=\"cbb-line\">}\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">add_action<\/span>( <span class=\"cbb-string\">'init'<\/span>, <span class=\"cbb-string\">'lw_category_posts_block_init'<\/span> );<\/span><\/code><\/pre><\/figure>\n\n\n<p>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 \u00e4lter unterst\u00fctzen, brauchen eine Pr\u00fcfung mit <code>function_exists()<\/code> und einen R\u00fcckfallweg, wie ihn die Dev-Note zu 6.8 beschreibt. Das Beispiel umgeht das mit \u201eRequires at least: 6.9\u201c, was es f\u00fcr den weiter unten gezeigten Import im Editor ohnehin ben\u00f6tigt. Ohne Build-Schritt reicht ein einzelner Aufruf:<\/p>\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap wp-block-code-beautifier-block-code\" data-lang=\"plain\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">Text<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-plain\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">\/\/ Single block, without a manifest: reads build\/category-posts\/block.json.\n<\/span><span class=\"cbb-line\">register_block_type( __DIR__ . '\/build\/category-posts' );<\/span><\/code><\/pre><\/figure>\n\n\n<p>Ein Detail wird leicht \u00fcbersehen: Das Manifest ist ein Build-Ergebnis. Eine in <code>src\/<\/code> ge\u00e4nderte <code>block.json<\/code> wirkt erst nach dem n\u00e4chsten Build, auch auf der Serverseite.<\/p>\n<h2>render.php und seine drei Variablen<\/h2>\n<p>Enth\u00e4lt <code>block.json<\/code> ein Feld <code>render<\/code>, legt WordPress bei der Registrierung einen <code>render_callback<\/code> an. Im Core-Quelltext ist das eine statische Closure, die die Ausgabepufferung startet, die Datei per <code>require<\/code> l\u00e4dt und den Puffer zur\u00fcckgibt. In der Datei stehen drei Variablen bereit: <code>$attributes<\/code> (Array der Block-Attribute, Standardwerte bereits eingesetzt), <code>$content<\/code> (der gespeicherte innere Inhalt, wichtig f\u00fcr Blocks mit inneren Blocks, hier immer leer) und <code>$block<\/code> (die Instanz von <code>WP_Block<\/code> mit Namen, Kontext und geparstem Block). Die Render-Datei des Beispiels:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-meta\">&lt;?php<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Server-side rendering of the lw\/category-posts block.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Variables provided by WordPress:<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @var array    $attributes Block attributes, defaults from block.json applied.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @var string   $content    Inner content (always empty for this block).<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @var WP_Block $block      Block instance: name, context, parsed block.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @package Lw<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">if<\/span> ( ! <span class=\"cbb-function\">defined<\/span>( <span class=\"cbb-string\">'ABSPATH'<\/span> ) ) {\n<\/span><span class=\"cbb-line\">\texit; <span class=\"cbb-comment\">\/\/ Exit if accessed directly.<\/span>\n<\/span><span class=\"cbb-line\">}\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_category_id<\/span> = <span class=\"cbb-keyword\">isset<\/span>( <span class=\"cbb-variable\">$attributes<\/span>&#91;<span class=\"cbb-string\">'categoryId'<\/span>&#93; ) ? <span class=\"cbb-function\">absint<\/span>( <span class=\"cbb-variable\">$attributes<\/span>&#91;<span class=\"cbb-string\">'categoryId'<\/span>&#93; ) : <span class=\"cbb-number\">0<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_count<\/span>       = <span class=\"cbb-keyword\">isset<\/span>( <span class=\"cbb-variable\">$attributes<\/span>&#91;<span class=\"cbb-string\">'count'<\/span>&#93; ) ? <span class=\"cbb-function\">absint<\/span>( <span class=\"cbb-variable\">$attributes<\/span>&#91;<span class=\"cbb-string\">'count'<\/span>&#93; ) : <span class=\"cbb-number\">5<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_count<\/span>       = <span class=\"cbb-function\">min<\/span>( <span class=\"cbb-function\">max<\/span>( <span class=\"cbb-variable\">$lw_count<\/span>, <span class=\"cbb-number\">1<\/span> ), <span class=\"cbb-number\">10<\/span> );\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_term<\/span>        = <span class=\"cbb-variable\">$lw_category_id<\/span> ? <span class=\"cbb-function\">get_term<\/span>( <span class=\"cbb-variable\">$lw_category_id<\/span>, <span class=\"cbb-string\">'category'<\/span> ) : <span class=\"cbb-constant\">null<\/span>;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ Render nothing until a valid category has been chosen.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">if<\/span> ( ! <span class=\"cbb-variable\">$lw_term<\/span> <span class=\"cbb-keyword\">instanceof<\/span> <span class=\"cbb-type\">WP_Term<\/span> ) {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">return<\/span>;\n<\/span><span class=\"cbb-line\">}\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ Do not list the post that contains the block (context from \"usesContext\").<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_current_id<\/span> = <span class=\"cbb-keyword\">isset<\/span>( <span class=\"cbb-variable\">$block<\/span><span class=\"cbb-operator\">-&gt;<\/span>context&#91;<span class=\"cbb-string\">'postId'<\/span>&#93; ) ? <span class=\"cbb-function\">absint<\/span>( <span class=\"cbb-variable\">$block<\/span><span class=\"cbb-operator\">-&gt;<\/span>context&#91;<span class=\"cbb-string\">'postId'<\/span>&#93; ) : <span class=\"cbb-number\">0<\/span>;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-variable\">$lw_posts<\/span> = <span class=\"cbb-function\">get_posts<\/span>(\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">array<\/span>(\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">'category'<\/span>     <span class=\"cbb-operator\">=&gt;<\/span> <span class=\"cbb-variable\">$lw_term<\/span><span class=\"cbb-operator\">-&gt;<\/span>term_id,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">'numberposts'<\/span>  <span class=\"cbb-operator\">=&gt;<\/span> <span class=\"cbb-variable\">$lw_count<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">'post_status'<\/span>  <span class=\"cbb-operator\">=&gt;<\/span> <span class=\"cbb-string\">'publish'<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">'has_password'<\/span> <span class=\"cbb-operator\">=&gt;<\/span> <span class=\"cbb-constant\">false<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">'exclude'<\/span>      <span class=\"cbb-operator\">=&gt;<\/span> <span class=\"cbb-variable\">$lw_current_id<\/span> ? <span class=\"cbb-keyword\">array<\/span>( <span class=\"cbb-variable\">$lw_current_id<\/span> ) : <span class=\"cbb-keyword\">array<\/span>(),\n<\/span><span class=\"cbb-line\">\t)\n<\/span><span class=\"cbb-line\">);\n<\/span><span class=\"cbb-line\"><span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\">&lt;div <span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">echo<\/span> <span class=\"cbb-function\">wp_kses_data<\/span>( <span class=\"cbb-function\">get_block_wrapper_attributes<\/span>() ); <span class=\"cbb-meta\">?&gt;<\/span>&gt;\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">if<\/span> ( <span class=\"cbb-keyword\">empty<\/span>( <span class=\"cbb-variable\">$lw_posts<\/span> ) ) : <span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-punct\">&lt;<\/span><span class=\"cbb-tag\">p<\/span><span class=\"cbb-punct\">&gt;<\/span><span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-function\">esc_html_e<\/span>( <span class=\"cbb-string\">'No posts in this category yet.'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ); <span class=\"cbb-meta\">?&gt;<\/span><span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">p<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">else<\/span> : <span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-punct\">&lt;<\/span><span class=\"cbb-tag\">ul<\/span> <span class=\"cbb-attr\">class<\/span><span class=\"cbb-punct\">=<\/span><span class=\"cbb-string\">\"wp-block-lw-category-posts__list\"<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">foreach<\/span> ( <span class=\"cbb-variable\">$lw_posts<\/span> <span class=\"cbb-keyword\">as<\/span> <span class=\"cbb-variable\">$lw_post<\/span> ) : <span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-punct\">&lt;<\/span><span class=\"cbb-tag\">li<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t<span class=\"cbb-punct\">&lt;<\/span><span class=\"cbb-tag\">a<\/span> <span class=\"cbb-attr\">href<\/span><span class=\"cbb-punct\">=<\/span><span class=\"cbb-string\">\"&lt;?php echo esc_url( get_permalink( $lw_post ) ); ?&gt;\"<\/span><span class=\"cbb-punct\">&gt;<\/span><span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">echo<\/span> <span class=\"cbb-function\">esc_html<\/span>( <span class=\"cbb-function\">get_the_title<\/span>( <span class=\"cbb-variable\">$lw_post<\/span> ) ); <span class=\"cbb-meta\">?&gt;<\/span><span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">a<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t<span class=\"cbb-punct\">&lt;<\/span><span class=\"cbb-tag\">time<\/span> <span class=\"cbb-attr\">datetime<\/span><span class=\"cbb-punct\">=<\/span><span class=\"cbb-string\">\"&lt;?php echo esc_attr( get_the_date( 'c', $lw_post ) ); ?&gt;\"<\/span><span class=\"cbb-punct\">&gt;<\/span><span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">echo<\/span> <span class=\"cbb-function\">esc_html<\/span>( <span class=\"cbb-function\">get_the_date<\/span>( <span class=\"cbb-string\">''<\/span>, <span class=\"cbb-variable\">$lw_post<\/span> ) ); <span class=\"cbb-meta\">?&gt;<\/span><span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">time<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">li<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">endforeach<\/span>; <span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">ul<\/span><span class=\"cbb-punct\">&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-meta\">&lt;?php<\/span> <span class=\"cbb-keyword\">endif<\/span>; <span class=\"cbb-meta\">?&gt;<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-punct\">&lt;\/<\/span><span class=\"cbb-tag\">div<\/span><span class=\"cbb-punct\">&gt;<\/span><\/span><\/code><\/pre><\/figure>\n\n\n<p>Mehrere Entscheidungen in dieser Datei folgen daraus, wie sie geladen wird. Da <code>require<\/code> f\u00fcr jede Block-Instanz auf einer Seite erneut l\u00e4uft, darf die Datei keine Funktionen oder Klassen deklarieren; eine zweite Instanz des Blocks endete sonst in einem fatalen Fehler. Hilfsfunktionen geh\u00f6ren in die Plugin-Hauptdatei oder in ein eigenes Include. F\u00fcr PHP_CodeSniffer sehen die Variablen global aus, deshalb tragen sie das Pr\u00e4fix <code>lw_<\/code>. Die Pr\u00fcfung stellt das allerdings nicht zufrieden: WPCS lehnt Pr\u00e4fixe mit weniger als vier Zeichen ab, und Plugin Check 2.1.0, das das erwartete Pr\u00e4fix aus dem Plugin-Code ableitet (hier aus dem Funktionsnamen <code>lw_category_posts_block_init<\/code>), meldet jede dieser Variablen als Warnung. Auf die Funktion des Blocks haben die Warnungen keinen Einfluss. <code>return<\/code> beendet die Datei vorzeitig und hinterl\u00e4sst einen leeren Puffer, so rendert ein Block gar nichts.<\/p>\n<p>Attribute sind Daten aus <code>post_content<\/code> und damit Eingaben. Die Schemapr\u00fcfung garantiert ganze Zahlen, aber keinen sinnvollen Bereich, daher begrenzen <code>absint()<\/code>, <code>min()<\/code> und <code>max()<\/code> die Anzahl auf 1 bis 10. <code>get_term()<\/code> stellt sicher, dass die Rubrik existiert. <code>get_posts()<\/code> ist eine d\u00fcnne H\u00fclle um <code>WP_Query<\/code>, die <code>ignore_sticky_posts<\/code>, <code>no_found_rows<\/code> und <code>suppress_filters<\/code> selbst setzt; das passt zu einer kurzen Liste ohne Seitennavigation. Wie weitere Abfrageargumente wie <code>meta_query<\/code> zu SQL werden, zeigt <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/meta_query-in-wp_query-wie-jede-klausel-zu-einem-join-wird\/\">der Beitrag zu <code>meta_query<\/code> in <code>WP_Query<\/code><\/a>. Jede Ausgabe l\u00e4uft durch die passende Escaping-Funktion: <code>esc_url()<\/code> f\u00fcr den Link, <code>esc_html()<\/code> f\u00fcr Titel und Datum, <code>esc_attr()<\/code> f\u00fcr das Attribut <code>datetime<\/code>. Die allgemeinen Regeln dazu behandelt <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/sanitizing-escaping-nonces-capabilities-plugin-review\/\">ein eigener Beitrag dieser Reihe zu Sanitizing, Escaping, Nonces und Capabilities<\/a>.<\/p>\n<p><code>get_block_wrapper_attributes()<\/code> liefert die Attribut-Zeichenkette f\u00fcr das umschlie\u00dfende Element: die Klasse <code>wp-block-lw-category-posts<\/code> plus Klassen und Inline-Styles aus den <code>supports<\/code>-Einstellungen. Die Funktion escapet jeden Wert selbst mit <code>esc_attr()<\/code>, wie ihr Quelltext zeigt. In der Liste der automatisch escapenden Funktionen der WordPress Coding Standards fehlt sie allerdings, sodass ein nacktes <code>echo<\/code> den Escaping-Sniff ausl\u00f6st; Plugin Check 2.1.0 meldet es als Fehler (<code>WordPress.Security.EscapeOutput.OutputNotEscaped<\/code>). <code>wp_kses_data()<\/code> l\u00e4sst eine Attribut-Zeichenkette ohne Tags unver\u00e4ndert und stellt die Pr\u00fcfung zufrieden; ein begr\u00fcndeter <code>phpcs:ignore<\/code>-Kommentar ist die Alternative.<\/p>\n<h2>Die Editorseite: edit.js mit ServerSideRender<\/h2>\n<p>In JavaScript registriert <code>index.js<\/code> den Block nur mit <code>edit<\/code>. Fehlt <code>save<\/code>, setzt die Block-API ein Standard-<code>save<\/code> ein, das <code>null<\/code> zur\u00fcckgibt:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"javascript\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JavaScript<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-javascript\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { registerBlockType } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/blocks'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> <span class=\"cbb-string\">'.\/style.scss'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> <span class=\"cbb-type\">Edit<\/span> <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'.\/edit'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> metadata <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'.\/block.json'<\/span>;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ No save(): the default save returns null, so only the block comment<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ with its attributes is stored in post_content.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">registerBlockType<\/span>( metadata.name, {\n<\/span><span class=\"cbb-line\">\tedit: <span class=\"cbb-type\">Edit<\/span>,\n<\/span><span class=\"cbb-line\">} );<\/span><\/code><\/pre><\/figure>\n\n\n<p><code>edit.js<\/code> liefert die Einstellungen in der Seitenleiste und eine Vorschau. Die Rubriken kommen aus dem Core-Datenspeicher, mit derselben Abfrage, die der Core-Block \u201eNeueste Beitr\u00e4ge\u201c verwendet. Die Vorschau erzeugt <code>ServerSideRender<\/code>: Die Komponente schickt die Attribute an die REST-Route <code>\/wp\/v2\/block-renderer\/lw\/category-posts<\/code> und f\u00fcgt das HTML ein, das <code>render.php<\/code> zur\u00fcckgibt:<\/p>\n\n\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"javascript\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JavaScript<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-javascript\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { __ } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/i18n'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { <span class=\"cbb-type\">InspectorControls<\/span>, useBlockProps } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/block-editor'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-type\">PanelBody<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-type\">Placeholder<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-type\">RangeControl<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-type\">SelectControl<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-type\">Spinner<\/span>,\n<\/span><span class=\"cbb-line\">} <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/components'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { useSelect } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/data'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { store as coreStore } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/core-data'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { decodeEntities } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/html-entities'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> { <span class=\"cbb-type\">ServerSideRender<\/span> } <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'@wordpress\/server-side-render'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> metadata <span class=\"cbb-keyword\">from<\/span> <span class=\"cbb-string\">'.\/block.json'<\/span>;\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">import<\/span> <span class=\"cbb-string\">'.\/editor.scss'<\/span>;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/\/ Same query shape as the core Latest Posts block uses for its category list.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">const<\/span> CATEGORIES_QUERY = { per_page: <span class=\"cbb-number\">-1<\/span>, _fields: <span class=\"cbb-string\">'id,name'<\/span>, context: <span class=\"cbb-string\">'view'<\/span> };\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">export<\/span> <span class=\"cbb-keyword\">default<\/span> <span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">Edit<\/span>( { attributes, setAttributes } ) {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">const<\/span> { categoryId, count } = attributes;\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">const<\/span> blockProps = <span class=\"cbb-function\">useBlockProps<\/span>();\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">const<\/span> categories = <span class=\"cbb-function\">useSelect<\/span>(\n<\/span><span class=\"cbb-line\">\t\t( select ) <span class=\"cbb-operator\">=&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-function\">select<\/span>( coreStore ).<span class=\"cbb-function\">getEntityRecords<\/span>(\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-string\">'taxonomy'<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-string\">'category'<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t\tCATEGORIES_QUERY\n<\/span><span class=\"cbb-line\">\t\t\t),\n<\/span><span class=\"cbb-line\">\t\t&#91;&#93;\n<\/span><span class=\"cbb-line\">\t);\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">const<\/span> options = &#91;\n<\/span><span class=\"cbb-line\">\t\t{ value: <span class=\"cbb-string\">'0'<\/span>, label: <span class=\"cbb-function\">__<\/span>( <span class=\"cbb-string\">'\u2014 Select a category \u2014'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ) },\n<\/span><span class=\"cbb-line\">\t\t...( categories ?? &#91;&#93; ).<span class=\"cbb-function\">map<\/span>( ( term ) <span class=\"cbb-operator\">=&gt;<\/span> ( {\n<\/span><span class=\"cbb-line\">\t\t\tvalue: <span class=\"cbb-function\">String<\/span>( term.id ),\n<\/span><span class=\"cbb-line\">\t\t\tlabel: <span class=\"cbb-function\">decodeEntities<\/span>( term.name ),\n<\/span><span class=\"cbb-line\">\t\t} ) ),\n<\/span><span class=\"cbb-line\">\t&#93;;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">return<\/span> (\n<\/span><span class=\"cbb-line\">\t\t&lt;&gt;\n<\/span><span class=\"cbb-line\">\t\t\t&lt;<span class=\"cbb-type\">InspectorControls<\/span>&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t&lt;<span class=\"cbb-type\">PanelBody<\/span> title={ <span class=\"cbb-function\">__<\/span>( <span class=\"cbb-string\">'Settings'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ) }&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t\t{ categories ? (\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t&lt;<span class=\"cbb-type\">SelectControl<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t__next40pxDefaultSize\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t__nextHasNoMarginBottom\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\tlabel={ <span class=\"cbb-function\">__<\/span>( <span class=\"cbb-string\">'Category'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\tvalue={ <span class=\"cbb-function\">String<\/span>( categoryId ) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\toptions={ options }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\tonChange={ ( value ) <span class=\"cbb-operator\">=&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t\t<span class=\"cbb-function\">setAttributes<\/span>( { categoryId: <span class=\"cbb-function\">Number<\/span>( value ) } )\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t}\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\/&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t\t) : (\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t&lt;<span class=\"cbb-type\">Spinner<\/span> \/&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t\t) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t&lt;<span class=\"cbb-type\">RangeControl<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t__next40pxDefaultSize\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t__nextHasNoMarginBottom\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tlabel={ <span class=\"cbb-function\">__<\/span>( <span class=\"cbb-string\">'Number of posts'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tvalue={ count }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tmin={ <span class=\"cbb-number\">1<\/span> }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tmax={ <span class=\"cbb-number\">10<\/span> }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tonChange={ ( value ) <span class=\"cbb-operator\">=&gt;<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t<span class=\"cbb-function\">setAttributes<\/span>( { count: value ?? <span class=\"cbb-number\">5<\/span> } )\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t}\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\/&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t&lt;\/<span class=\"cbb-type\">PanelBody<\/span>&gt;\n<\/span><span class=\"cbb-line\">\t\t\t&lt;\/<span class=\"cbb-type\">InspectorControls<\/span>&gt;\n<\/span><span class=\"cbb-line\">\t\t\t&lt;div { ...blockProps }&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t{ categoryId ? (\n<\/span><span class=\"cbb-line\">\t\t\t\t\t&lt;<span class=\"cbb-type\">ServerSideRender<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tblock={ metadata.name }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tattributes={ attributes }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tskipBlockSupportAttributes\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\/&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t) : (\n<\/span><span class=\"cbb-line\">\t\t\t\t\t&lt;<span class=\"cbb-type\">Placeholder<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\ticon=<span class=\"cbb-string\">\"list-view\"<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tlabel={ <span class=\"cbb-function\">__<\/span>( <span class=\"cbb-string\">'Category Posts'<\/span>, <span class=\"cbb-string\">'category-posts'<\/span> ) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\tinstructions={ <span class=\"cbb-function\">__<\/span>(\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t<span class=\"cbb-string\">'Choose a category in the block settings.'<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t\t<span class=\"cbb-string\">'category-posts'<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\t) }\n<\/span><span class=\"cbb-line\">\t\t\t\t\t\/&gt;\n<\/span><span class=\"cbb-line\">\t\t\t\t) }\n<\/span><span class=\"cbb-line\">\t\t\t&lt;\/div&gt;\n<\/span><span class=\"cbb-line\">\t\t&lt;\/&gt;\n<\/span><span class=\"cbb-line\">\t);\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n\n\n<p>In der mit WordPress 6.9 ausgelieferten Paketversion (6.9.5) exportiert <code>@wordpress\/server-side-render<\/code> die Komponente <code>ServerSideRender<\/code> bereits als benannten Export; der Standard-Export gilt als veraltet und bleibt als Kompatibilit\u00e4tsschicht erhalten. Dieser benannte Import ist der Grund f\u00fcr \u201eRequires at least: 6.9\u201c im Plugin-Header. <code>skipBlockSupportAttributes<\/code> nimmt die Style-Attribute aus der Anfrage, denn das \u00e4u\u00dfere <code>div<\/code> erh\u00e4lt Farben und Abst\u00e4nde schon \u00fcber <code>useBlockProps()<\/code>; sonst erschienen sie doppelt. Die Komponente h\u00e4ngt au\u00dferdem die ID des aktuellen Beitrags an die Anfrage, sodass der Kontext <code>postId<\/code> in <code>render.php<\/code> auch im Editor greift. Die beiden Props <code>__next\u2026<\/code> an den Steuerelementen schalten \u00e4ltere WordPress-Versionen auf die aktuelle Gr\u00f6\u00dfe und Abst\u00e4nde der Controls; laut Changelog der Components sind beide inzwischen Standard und k\u00f6nnen entfallen, sobald diese Versionen nicht mehr unterst\u00fctzt werden.<\/p>\n<figure class=\"lw-diagram\">\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/wp-dynamischer-block-de.png?v=20260930\" width=\"1120\" height=\"580\" alt=\"Dynamischer Block: welche Datei wo und wann gelesen wird, in drei Spalten f\u00fcr jeden PHP-Aufruf, den Block-Editor und das Frontend; blocks-manifest.php registriert alle Bl\u00f6cke, der Editor l\u00e4dt index.js mit ServerSideRender-Vorschau, und render.php baut das Markup aus den in post_content gespeicherten Attributen\">\n<figcaption>PHP registriert aus dem Manifest, der Editor l\u00e4dt das Bundle, render.php erzeugt Markup f\u00fcr Frontend und Vorschau<\/figcaption>\n<\/figure>\n<p>Das Diagramm fasst zusammen, wer welche Datei wann liest: PHP liest das Manifest bei jedem Aufruf, der Editor l\u00e4dt das Skript-Bundle und beide Stylesheets, und <code>render.php<\/code> l\u00e4uft sowohl f\u00fcr das Frontend als auch f\u00fcr die Editor-Vorschau \u00fcber die REST-Route.<\/p>\n<h2>Statisch oder dynamisch: was in der Datenbank landet<\/h2>\n<p>F\u00fcr das Beispiel steht in der Datenbank nur der Block-Kommentar. Attribute, die ihrem Standardwert entsprechen, werden nicht serialisiert:<\/p>\n\n\n<figure style=\"--cbb-digits:1\" class=\"cbb cbb--wrap wp-block-code-beautifier-block-code\" data-lang=\"plain\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">Text<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-plain\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">&lt;!-- wp:lw\/category-posts {\"categoryId\":7,\"count\":3} \/--&gt;<\/span><\/code><\/pre><\/figure>\n\n\n<p>Hier liegt der Kern des Unterschieds. Ein statischer Block speichert die Ausgabe seiner <code>save<\/code>-Funktion als HTML zwischen den Kommentar-Begrenzern, und der Editor vergleicht dieses HTML beim \u00d6ffnen des Beitrags mit einem frischen Aufruf von <code>save<\/code>. Jede \u00c4nderung am Markup (eine zus\u00e4tzliche Klasse, ein Wrapper, ein anderes Tag) bricht deshalb bestehende Inhalte, solange kein Array <code>deprecated<\/code> die alten Fassungen beschreibt. Beim dynamischen Block gibt <code>save<\/code> <code>null<\/code> zur\u00fcck, der Editor \u00fcberspringt die Markup-Pr\u00fcfung, und eine \u00c4nderung an <code>render.php<\/code> erreicht nach dem n\u00e4chsten Deployment jedes Vorkommen des Blocks. Das Block-Editor-Handbuch nennt genau diese beiden F\u00e4lle f\u00fcr dynamisches Rendern: Inhalte, die sich \u00e4ndern sollen, ohne dass der Beitrag aktualisiert wird, und Markup, das sich voraussichtlich \u00e4ndert.<\/p>\n<p>Dynamisches Rendern verursacht zus\u00e4tzliche Arbeit auf dem Server: Jeder Seitenaufruf mit dem Block f\u00fchrt eine Datenbankabfrage aus, jede Attribut\u00e4nderung im Editor eine REST-Anfrage. F\u00fcr 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\u00f6rt nicht zum Beispiel, und f\u00fcr diesen Beitrag wurde nichts gemessen. Au\u00dferdem h\u00e4ngt die Ausgabe vom aktiven Plugin ab: Ohne das Plugin rendert der Block nichts, weil <code>post_content<\/code> kein Ersatz-HTML enth\u00e4lt.<\/p>\n<h2>Grenzen und offene Punkte<\/h2>\n<ul>\n<li>Die Dokumentation von <code>ServerSideRender<\/code> bezeichnet die Komponente als R\u00fcckfall- oder Altmechanismus, der f\u00fcr die Entwicklung neuer Funktionen nicht geeignet ist. F\u00fcr Blocks, deren Vorschau sich in JavaScript aus REST-Daten bauen l\u00e4sst, gilt clientseitiges Rendern als empfohlener Weg. Das Beispiel nutzt die Komponente bewusst, damit <code>render.php<\/code> die einzige Quelle des Markups bleibt; der Preis ist ein verschachtelter Wrapper im Editor (das \u00e4u\u00dfere <code>div<\/code> aus <code>useBlockProps()<\/code> enth\u00e4lt das <code>div<\/code> aus <code>render.php<\/code>).<\/li>\n<li>WordPress 7.0 hat die Registrierung von Blocks allein in PHP eingef\u00fchrt: Mit <code>'autoRegister' =&gt; true<\/code> unter <code>supports<\/code> und einem <code>render_callback<\/code> erscheint ein Block ohne eigenes JavaScript im Editor, und f\u00fcr Attribute vom Typ string, integer, boolean und enum entstehen Steuerelemente automatisch. Eine Rubrikenauswahl mit Namen statt numerischer IDs deckt das nicht ab, deshalb beh\u00e4lt das Beispiel eine eigene <code>edit.js<\/code>.<\/li>\n<li>Das beschriebene Ger\u00fcst entspricht <code>@wordpress\/create-block<\/code> 4.99.0. Das Paket erscheint in kurzen Abst\u00e4nden; Dateinamen, Schalter und Vorgaben k\u00f6nnen sich mit sp\u00e4teren Versionen \u00e4ndern.<\/li>\n<li>Plugin Check 2.1.0 meldet f\u00fcr das Beispiel unter WordPress 7.1.2 keine Fehler (gepr\u00fcft am 30.09.2026). F\u00fcr jede Kopie von <code>render.php<\/code> in <code>src\/<\/code> und <code>build\/<\/code> listet es sieben <code>PrefixAllGlobals<\/code>-Warnungen und eine Warnung zum Parameter <code>exclude<\/code> von <code>get_posts()<\/code>; ein nacktes <code>echo get_block_wrapper_attributes()<\/code> meldet es als Fehler.<\/li>\n<li>Caching der gerenderten Liste und die Kosten der REST-Vorschau wurden nicht gemessen.<\/li>\n<\/ul>\n\n<div class=\"lw-faq\">\n<h2>Fragen und Antworten<\/h2>\n\n<h3>Warum wirkt eine \u00c4nderung an block.json nicht, obwohl die Datei gespeichert ist?<\/h3>\n<p>Registriert wird aus <code>build\/blocks-manifest.php<\/code>, nicht aus <code>src\/<\/code>. Das Manifest entsteht beim Build; erst nach <code>npm run build<\/code> oder bei laufendem <code>npm start<\/code> sieht auch PHP die neue Fassung.<\/p>\n\n<h3>Darf render.php eigene Hilfsfunktionen deklarieren?<\/h3>\n<p>Nein. WordPress l\u00e4dt die Datei per <code>require<\/code> f\u00fcr jede Block-Instanz erneut. Steht der Block zweimal auf einer Seite, f\u00fchrt eine zweite Deklaration derselben Funktion zu einem fatalen Fehler. Hilfsfunktionen geh\u00f6ren mit Pr\u00e4fix in die Plugin-Hauptdatei oder in ein eigenes Include.<\/p>\n\n<h3>Braucht ein serverseitig gerenderter Block seit WordPress 7.0 \u00fcberhaupt noch JavaScript?<\/h3>\n<p>Nicht zwingend. Mit <code>'autoRegister' =&gt; true<\/code> unter <code>supports<\/code> und einem <code>render_callback<\/code> erscheint ein Block ohne eigenes JavaScript im Editor; Steuerelemente entstehen f\u00fcr Attribute vom Typ string, integer, boolean und enum. F\u00fcr eine Auswahlliste mit Rubriknamen oder eigene Platzhalter bleibt eine <code>edit.js<\/code> n\u00f6tig.<\/p>\n<\/div>\n\n<div class=\"lw-quellen\">\n<h2>Quellen<\/h2>\n<ul>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-create-block\/\" target=\"_blank\" rel=\"noopener noreferrer\">@wordpress\/create-block \u2013 Block Editor Handbook<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/trunk\/packages\/create-block\/lib\" target=\"_blank\" rel=\"noopener noreferrer\">Quelltext von create-block: templates.js, init-package-json.js, Plugin- und Block-Vorlagen (GitHub)<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/@wordpress\/create-block\" target=\"_blank\" rel=\"noopener noreferrer\">@wordpress\/create-block auf npm, Version 4.99.0<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/tutorial\/\" target=\"_blank\" rel=\"noopener noreferrer\">Tutorial: Build your first block<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-metadata\/\" target=\"_blank\" rel=\"noopener noreferrer\">Metadata in block.json<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-api-versions\/\" target=\"_blank\" rel=\"noopener noreferrer\">Block API Versions<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/fundamentals\/static-dynamic-rendering\/\" target=\"_blank\" rel=\"noopener noreferrer\">Static or Dynamic rendering of a block<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/fundamentals\/registration-of-a-block\/\" target=\"_blank\" rel=\"noopener noreferrer\">Registration of a block<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-deprecation\/\" target=\"_blank\" rel=\"noopener noreferrer\">Deprecation<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-scripts\/\" target=\"_blank\" rel=\"noopener noreferrer\">@wordpress\/scripts<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/dependency-extraction-webpack-plugin\/README.md\" target=\"_blank\" rel=\"noopener noreferrer\">Dependency Extraction Webpack Plugin (README)<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-server-side-render\/\" target=\"_blank\" rel=\"noopener noreferrer\">@wordpress\/server-side-render<\/a> und <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/server-side-render\/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\">Changelog des Pakets<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/get_block_wrapper_attributes\/\" target=\"_blank\" rel=\"noopener noreferrer\">get_block_wrapper_attributes()<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_register_block_types_from_metadata_collection\/\" target=\"_blank\" rel=\"noopener noreferrer\">wp_register_block_types_from_metadata_collection()<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2024\/10\/17\/new-block-type-registration-apis-to-improve-performance-in-wordpress-6-7\/\" target=\"_blank\" rel=\"noopener noreferrer\">New block type registration APIs to improve performance in WordPress 6.7<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2025\/03\/13\/more-efficient-block-type-registration-in-6-8\/\" target=\"_blank\" rel=\"noopener noreferrer\">More efficient block type registration in 6.8<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2026\/03\/03\/php-only-block-registration\/\" target=\"_blank\" rel=\"noopener noreferrer\">PHP-only block registration<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-includes\/blocks.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop, Branch 7.1: blocks.php<\/a> und <a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-includes\/post.php\" target=\"_blank\" rel=\"noopener noreferrer\">post.php (get_posts)<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/WordPress-Coding-Standards\/blob\/develop\/WordPress\/Helpers\/EscapingFunctionsTrait.php\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress Coding Standards: EscapingFunctionsTrait.php<\/a> (abgerufen am 29.09.2026)<\/li>\n<li><a href=\"https:\/\/api.wordpress.org\/core\/version-check\/1.7\/\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress-Versionsabfrage (aktuelle Version 7.1.2)<\/a> (abgerufen am 29.09.2026)<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Ein serverseitig gerenderter Block listet die letzten Beitr\u00e4ge einer Rubrik: Ger\u00fcst mit create-block, block.json, Registrierung per Manifest, render.php und Vorschau im Editor.<\/p>\n","protected":false},"author":1,"featured_media":20913,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[92644],"tags":[92268,92274,92235,92759,92277],"class_list":["post-20867","post","type-post","status-publish","format-standard","hentry","category-tutorials-de-wordpress-plugins-tricks-de","tag-javascript-de","tag-php-de","tag-tutorial-de","tag-web-development-de","tag-wordpress-de"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-21T09:47:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Ein dynamischer Gutenberg-Block mit block.json und render.php\",\"datePublished\":\"2026-10-21T09:47:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/\"},\"wordCount\":2231,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20867-dynamischer-block-block-json.png\",\"keywords\":[\"JavaScript\",\"PHP\",\"Tutorial\",\"Web Development\",\"WordPress\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/\",\"name\":\"Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20867-dynamischer-block-block-json.png\",\"datePublished\":\"2026-10-21T09:47:00+00:00\",\"description\":\"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20867-dynamischer-block-block-json.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20867-dynamischer-block-block-json.png\",\"width\":1200,\"height\":630,\"caption\":\"Ein dynamischer Gutenberg-Block mit block.json und render.php\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/dynamischer-block-block-json\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Ein dynamischer Gutenberg-Block mit block.json und render.php\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik","description":"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/","og_locale":"en_US","og_type":"article","og_title":"Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik","og_description":"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-21T09:47:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Ein dynamischer Gutenberg-Block mit block.json und render.php","datePublished":"2026-10-21T09:47:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/"},"wordCount":2231,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png","keywords":["JavaScript","PHP","Tutorial","Web Development","WordPress"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/","url":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/","name":"Ein dynamischer Gutenberg-Block mit block.json und render.php | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png","datePublished":"2026-10-21T09:47:00+00:00","description":"Wie ein dynamischer WordPress-Block heute entsteht: create-block --variant dynamic, block.json mit apiVersion 3, blocks-manifest.php und render.php.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20867-dynamischer-block-block-json.png","width":1200,"height":630,"caption":"Ein dynamischer Gutenberg-Block mit block.json und render.php"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/dynamischer-block-block-json\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Ein dynamischer Gutenberg-Block mit block.json und render.php"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20867","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=20867"}],"version-history":[{"count":2,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20867\/revisions"}],"predecessor-version":[{"id":22772,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20867\/revisions\/22772"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/20913"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=20867"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=20867"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20867"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}