Contents
Overview — what this tutorial covers
This article explains in complete detail how to save Gutenberg block data into post meta (the wp_postmeta table) using PHP. It shows the recommended, secure approach that integrates with the REST API so block attributes stored as post meta are editable in the block editor, and it includes alternate approaches (parsing blocks on save) for migration or special cases. Every PHP and JavaScript example is included so you can copy and adapt for your plugin or theme.
Concepts and terminology
- Block attribute — a piece of data belonging to a block instance in the editor. Attributes may be stored in block markup, in block comment delimiters, or in post meta.
- Post meta — key/value metadata stored in wp_postmeta. Accessed by get_post_meta(), update_post_meta(), delete_post_meta().
- REST exposure — for editor support the meta must be exposed in the REST API (show_in_rest) so the block editor can read and write it.
- register_post_meta() — a WordPress PHP function to register a post meta key and its REST schema, sanitization and auth callbacks.
- source: meta — in block attributes (block.json or registerBlockType) this tells the editor the attribute value is stored in post meta.
When to store block data in post meta
- When the data represents a property of the entire post (not just a block instance) that multiple blocks or templates will rely on.
- When you want easy server-side access (templates, queries, REST) without parsing post content.
- When you need to query by the value via meta queries or use it in template logic.
- When you store per-post settings separated from block markup (for migration/future changes).
High-level steps
- Register the post meta in PHP with register_post_meta(), including show_in_rest and a proper schema.
- Declare the block attribute to use source: meta and the meta key (in block.json or registerBlockType).
- Ensure sanitization and authorization are correct in PHP (sanitize_callback and auth_callback).
- Optionally, implement server-side rendering (render_callback) or save_post-based migration for legacy blocks.
- Test, debug, and ensure output is escaped in templates.
Step 1 — register the meta in PHP
You must register the meta key so the REST API exposes it and so WordPress knows the data schema and how to sanitize/authorize changes. Use register_post_meta(). Replace the meta key with a plugin-prefixed string (avoid collisions).
Example: register a single string meta key that is editable via the REST API and sanitized with sanitize_text_field(). Put this in your plugin or theme (init hook).
lt?php function myplugin_register_meta() { register_post_meta( post, // post type use page or an array for specific types myplugin_block_setting, // meta key (use a unique prefix) array( show_in_rest =gt array( schema =gt array( type =gt string, description =gt My block setting saved to post meta., context =gt array( view, edit ), ), ), single =gt true, // true => one value false => array of values type =gt string, sanitize_callback =gt sanitize_text_field, auth_callback =gt function( allowed, meta_key, post_id, user ) { // Only allow users who can edit the post to update meta via REST. return current_user_can( edit_post, post_id ) }, ) ) } add_action( init, myplugin_register_meta ) ?gt
Notes about the register_post_meta() arguments
- show_in_rest must be set (true or a REST schema) for editor support. If you pass just true, WordPress will expose the meta but won’t provide a JSON schema. Providing a schema is better for type safety.
- single — set false if you expect multiple values stored as separate rows in wp_postmeta for the same key (less common for block attributes).
- sanitize_callback — sanitize before storing. For HTML content, use wp_kses_post or a more specific sanitization.
- auth_callback — secure who can edit the meta in REST requests. For example, require edit_post capability.
- Prefer using a unique prefix for meta keys (e.g. myplugin_…) and avoid leading underscores unless you want a hidden meta key in the admin UI.
Step 2 — declare block attribute with source: meta
Two common ways to define a block are via block.json (recommended for API version 2 blocks) or registerBlockType in JavaScript. In either case declare an attribute that uses source: meta and the same meta key you registered in PHP. The editor will then read and write the meta via REST automatically.
block.json example (recommended)
{ apiVersion: 2, name: myplugin/my-block, title: My Block, category: widgets, attributes: { pluginSetting: { type: string, source: meta, meta: myplugin_block_setting } }, editorScript: file:./build/index.js, script: file:./build/frontend.js }
registerBlockType example (JS)
import { registerBlockType } from @wordpress/blocks import { TextControl } from @wordpress/components import { useBlockProps } from @wordpress/block-editor import { useSelect, useDispatch } from @wordpress/data registerBlockType( myplugin/my-block, { apiVersion: 2, title: My Block, category: widgets, attributes: { pluginSetting: { type: string, source: meta, meta: myplugin_block_setting } }, edit: ( props ) =gt { const { attributes, setAttributes } = props const { pluginSetting } = attributes return ( ltdiv { ...useBlockProps() }gt ltTextControl label=Plugin setting value={ pluginSetting } onChange={ ( value ) =gt setAttributes( { pluginSetting: value } ) } /gt lt/divgt ) }, save: () =gt { // Attribute stored in post meta — block saves nothing (or the block can render static markup) return null } } )
Important matching rules
- The meta key in PHP register_post_meta() must match the meta value in the attribute declaration exactly.
- The type declared in the REST schema should match the attribute type (string, boolean, number, object, array).
- If the attribute uses arrays/objects, reflect that in the schema and set single to false if storing multiple values in separate rows otherwise store a JSON-encoded value as a single string/object with single=true.
Server-side rendering and save()
If your block is dynamic (rendered on the server), use a render_callback in register_block_type() that reads post meta via get_post_meta() and outputs markup. If you declared attributes with source: meta you still might set save to return null for dynamic blocks.
lt?php function myplugin_register_block() { register_block_type( __DIR__ . /build/my-block, array( render_callback =gt myplugin_render_my_block, ) ) } add_action( init, myplugin_register_block ) function myplugin_render_my_block( attributes, content ) { post_id = get_the_ID() value = get_post_meta( post_id, myplugin_block_setting, true ) value_safe = esc_html( value ) // escape appropriately return ltdiv class=myplugin-blockgtValue: {value_safe}lt/divgt } ?gt
Alternative: parse blocks and save meta on post save (migration or advanced cases)
If you have existing block data inside post content (attributes saved in block markup) and want to migrate to post meta, or if you need server-side logic to transform or validate values at save time, use the save_post or wp_insert_post_data hooks and parse the post content with parse_blocks(). That gives you programmatic access to block attributes and allows you to update_post_meta() accordingly.
lt?php function myplugin_save_post_parse_blocks( post_id, post, update ) { // Avoid autosaves, revisions, and non-capable users. if ( wp_is_post_autosave( post_id ) wp_is_post_revision( post_id ) ) { return } if ( ! current_user_can( edit_post, post_id ) ) { return } // Parse blocks from post content and search for our block. blocks = parse_blocks( post->post_content ) found_value = null walker = new WP_Block_Parser_Block( blocks ) // pseudo-walker parse recursively below // Instead, implement a recursive function to walk blocks: search_block = function ( blocks ) use ( ampsearch_block, ampfound_value ) { foreach ( blocks as block ) { if ( isset( block[blockName] ) block[blockName] === myplugin/my-block ) { if ( isset( block[attrs][pluginSetting] ) ) { found_value = block[attrs][pluginSetting] return } } if ( ! empty( block[innerBlocks] ) ) { search_block( block[innerBlocks] ) if ( null !== found_value ) { return } } } } search_block( blocks ) if ( null !== found_value ) { // sanitize and save to post meta sanitized = sanitize_text_field( found_value ) update_post_meta( post_id, myplugin_block_setting, sanitized ) } } add_action( save_post, myplugin_save_post_parse_blocks, 10, 3 ) ?gt
Notes about parsing blocks
- Use parse_blocks() to get an array representation of all blocks in the post content.
- Be careful with performance: parsing many large posts in bulk operations may be expensive.
- When migrating content once, consider a one-time WP-CLI script that parses and migrates all posts, rather than hooking save_post long-term.
Sanitization and escaping best practices
- Sanitize input before it is saved (sanitize_callback in register_post_meta or sanitize_ functions when you call update_post_meta).
- When outputting meta values in front-end templates use esc_html(), esc_attr(), or wp_kses_post() depending on allowed markup.
- Define a restrictive REST schema to prevent unexpected types from being stored by malformed requests.
- Use auth_callback to restrict who can update meta via REST. Without proper auth, any authenticated user could attempt to modify meta keys.
Examples — end-to-end minimal plugin skeleton
Below is a minimal PHP plugin file that registers meta and registers a block type using block.json (assuming your block.json and JS build files are in build/). This is the recommended structure for a small plugin.
lt?php / Plugin Name: MyPlugin Block Meta Example Description: Demonstrates storing block attribute in post meta. Version: 1.0 Author: Example / if ( ! defined( ABSPATH ) ) { exit } function myplugin_register_meta_and_block() { // Register the meta for posts register_post_meta( post, myplugin_block_setting, array( show_in_rest => array( schema =gt array( type =gt string, ), ), single =gt true, type =gt string, sanitize_callback =gt sanitize_text_field, auth_callback =gt function( allowed, meta_key, post_id, user ) { return current_user_can( edit_post, post_id ) }, ) ) // Register block type using block.json in build folder register_block_type( __DIR__ . /build/my-block ) } add_action( init, myplugin_register_meta_and_block ) ?gt
Troubleshooting
- Meta never saves: Confirm register_post_meta() ran (hooked to init) and that show_in_rest is true and the meta key in your block attribute exactly matches the registered meta key.
- Editor does not show saved value: Check the REST response for the post in the Network panel. The meta may not be exposed or the schema type may be mismatched.
- Value saved but disappears on refresh: Ensure auth_callback allows the current user to edit post meta and sanitize_callback doesnt nullify valid values.
- Type mismatches: If you declared a boolean in schema but send a string, the value may not be accepted or may be coerced. Keep types consistent.
- Private meta keys: Leading underscore meta keys are hidden in admin the REST still uses them if registered, but prefer non-underscored keys for clarity.
Testing and debugging tips
- Open browser devtools gt Network gt Filters gt XHR and watch the REST request to /wp/v2/posts/ID when updating the block. Confirm the meta field is included in the request payload and that the response contains the updated meta.
- Inspect actual DB values with SELECT meta_value FROM wp_postmeta WHERE post_id = X AND meta_key = myplugin_block_setting.
- Use error_log() or WordPress debugging constants to log during development do not leave debugging logs or permissive auth callbacks in production code.
- Write a WP-CLI command for migration tasks so you can iterate on migrations without running them on every save.
Advanced considerations
- Indexing and querying: If you plan meta queries on this key, ensure your meta values are structured for efficient querying (strings vs serialized arrays). Consider WP_Meta_Query and its limitations.
- Multisite: Meta keys live per-site. Register meta on each site or during site creation as needed.
- Multiple block instances: If you have multiple instances of the same block and want a per-instance value, post meta is generally not a fit. Meta is per-post to store per-instance data you usually store attributes inside block markup or maintain a structured meta array keyed by block GUIDs.
- Migration path: Use parse_blocks() and WP-CLI for controlled migration from inline attributes to post meta, as shown earlier.
References
Checklist before shipping
- Meta keys are prefixed and unique.
- register_post_meta() uses a proper REST schema and sanitize_callback.
- auth_callback restricts who can modify the meta.
- Block attribute meta name exactly matches registered meta key.
- Output is escaped when rendering front-end templates.
- For migrations, use parse_blocks() and prefer a one-time WP-CLI migration rather than keeping heavy save_post processing on every save.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |