Why isn't my custom Gutenburg dynamic block saving the value?

I am creating a dynamic Gutenburg block. In it there is a rich text field as described by the docs. I see the value change on the page when I'm changing the value of the rich text field. Also the "Save" button becomes available and when I click it everything seems to be fine. In the console there are no errors. But when I refresh the Wordpress Admin page, the value is not visible anymore and the block is reset to it's original placeholder value for the rich text field. Files Given I have the following block.js: { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "namespace/hero", "version": "0.1.0", "title": "Hero", "category": "media", "icon": "cover-image", "description": "Example block scaffolded with Create Block tool.", "example": {}, "supports": { "html": false }, "textdomain": "hero", "editorScript": "file:./index.js", "viewScript": "file:./view.js", "render": "file:./render.php", "attributes": { "content": { "type": "string", "source": "html", "selector": "h2" } } } And this index.js: import { registerBlockType } from '@wordpress/blocks'; import Edit from './edit'; import metadata from './block.json'; registerBlockType( metadata.name, { edit: Edit, // because it's a dynamic block this should return null? save: () => null, }); Then edit.js: import { useBlockProps, RichText } from '@wordpress/block-editor'; export default function Edit({ attributes, setAttributes }) { const blockProps = useBlockProps({ className: `h-96 bg-amber-200 flex p-20`, }); return ( <div { ...blockProps }> <RichText tagName="h2" className="mt-10" value={ attributes.content } allowedFormats={[ 'core/bold', 'core/italic' ]} onChange={( newContent ) => { setAttributes({ content: newContent }); }} placeholder="Type here..." /> </div> ); } And I register the custom block as follows: <?php function create_custom_blocks_init(): void { $build_dir = dirname(__DIR__) . '/build'; $directories = glob($build_dir . '/*' , GLOB_ONLYDIR); foreach ($directories as $directory) { register_block_type($directory); } } add_action( 'init', 'create_custom_blocks_init' ); Additional info I do see a POST request happening to http://localhost:8000/index.php?rest_route=/wp/v2/pages/11&_locale=user. It has the following request body: { "id":11, "content":"<!-- wp:namespace/hero /-->" } and the response: { "id":11, "date":"2024-08-11T20:14:48", "date_gmt":"2024-08-11T20:14:48", "guid":{ "rendered":"http:\/\/localhost:8000\/?page_id=11", "raw":"http:\/\/localhost:8000\/?page_id=11" }, "modified":"2024-08-12T15:25:13", "modified_gmt":"2024-08-12T15:25:13", "password":"", "slug":"home", "status":"publish", "type":"page", "link":"http:\/\/localhost:8000\/", "title":{ "raw":"Home", "rendered":"Home" }, "content":{ "raw":"<!-- wp:namespace\/hero \/-->", "rendered":"<p class=\"h-screen bg-amber-200 flex p-20 wp-block-namespace-hero\">\n\tHero \u2013 hello from a dynamic block!<\/p>\n", "protected":false, "block_version":1 }, "excerpt":{ "raw":"", "rendered":"", "protected":false }, "author":1, "featured_media":0, "parent":0, "menu_order":0, "comment_status":"closed", "ping_status":"closed", "template":"templates\/home.php", "meta":{ "footnotes":"" }, "permalink_template":"http:\/\/localhost:8000\/", "generated_slug":"home", "class_list":[ "post-11", "page", "type-page", "status-publish", "hentry" ], "_links":{ "self":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11" } ], "collection":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages" } ], "about":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/types\/page" } ], "author":[ { "embeddable":true, "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/users\/1" } ], "replies":[ { "embeddable":true, "href":"http:\/\/localhost:8000\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11" } ], "version-history":[ { "count":30, "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11\/revisions" } ], "predecessor-version":[ { "id":57, "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11\/revisions\/57" } ], "wp:attachment":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11" } ], "wp:action-publish":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11" } ], "wp:action-unfiltered-html":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11" } ], "wp:action-assign-author":[ { "href":"http:\/\/localhost:8000\/index.php?rest_route=\/wp\/v2\/pages\/11" } ], "curies":[ { "name":"wp", "href":"https:\/\/api.w.org\/{rel}", "templated":true } ] } }

Comment (1)

Jese Leos

August 19, 2024

Verified user

As it turns out I misunderstood what a "dynamic block" is. If you want to save data and not only have dynamic data, use a static block. "static blocks, which have a fixed HTML structure saved in the database, “dynamic blocks” rely on server-side processing to construct their output dynamically." So it turns out I can remove render.php and instead implement save.js: Documentation on static or dynamic rendering

You’ll be in good company