Contents
Overview
This tutorial explains, in exhaustive detail, how to synchronize WordPress post meta fields and Gutenberg block attributes using JavaScript and the WordPress editor APIs. You will learn the concepts, required server-side registration, different synchronization strategies (declarative attribute-to-meta, immediate two-way sync, and server-rendered approaches), and robust example code for common patterns. Code samples are provided for PHP, block.json, and JavaScript edit components. Follow the sections in order for a complete working solution.
When and why synchronize meta and block attributes?
- Use case 1: Multiple blocks or UI controls need to share a single piece of post-level data (for example: a global subtitle stored in post meta and editable from a block).
- Use case 2: You want a block attribute to be persisted as post meta so the value is accessible to other parts of PHP (theme/templates, server-rendered blocks, etc.).
- Use case 3: You need real-time synchronization between the editor UI and the post meta (so other blocks immediately reflect updates).
Concepts and APIs
Key concepts and APIs you will use:
- register_post_meta() (PHP): register the meta key with show_in_rest so the REST API exposes it to Gutenberg.
- block.json attributes mapping: attributes can use source: meta and meta: your_meta_key so Gutenberg treats that attribute as a reflection of post meta at load/save.
- core/editor store (select/dispatch): use select(core/editor).getEditedPostAttribute(meta) to read, and dispatch(core/editor).editPost({ meta }) to update meta.
- useEntityProp (from @wordpress/core-data): cleaner hook to read/write the posts meta object.
- setAttributes in block edit: updates the block attribute within the block instance (editor state).
Prerequisites
- WordPress 5.5 (REST meta behavior and editor APIs are stable in modern WP versions).
- Block development environment (create-block or custom plugin) with ESNext build pipeline.
- Permission to register post meta on the server (add PHP in your plugin or theme).
Server-side: Register meta the right way
Before relying on meta in the block editor, register the meta key(s) with show_in_rest true. Provide type, single, and optionally a sanitize callback and auth_callback for security.
lt?php add_action( init, function() { register_post_meta( post, my_plugin_subtitle, array( show_in_rest => array( schema =gt array( type =gt string, context =gt array( edit, view ), ), ), single =gt true, type =gt string, auth_callback =gt function() { return current_user_can( edit_posts ) }, sanitize_callback =gt sanitize_text_field, ) ) } ) ?gt
Notes:
- Use the correct post type slug in place of post if you register meta for custom post types.
- For object/array meta, use type =gt object and include a schema. Keep in mind REST serialization rules.
- auth_callback is optional but recommended if you want to restrict who can edit the meta.
Option A — Declarative attribute-to-meta mapping (block.json)
If a block attribute should be a direct reflection of post meta at load/save, declare the attribute in block.json to use source: meta. This is the simplest approach and requires minimal code.
{ apiVersion: 2, name: my-plugin/subtitle-block, title: Subtitle Block, category: text, attributes: { subtitle: { type: string, source: meta, meta: my_plugin_subtitle } } }
Behavior and caveats:
- When the post is loaded, Gutenberg will populate the attribute value from the post meta exposed via the REST API.
- On save, the attribute is persisted to the post meta (if the server-side meta registration is correct).
- This method persists on save, not necessarily immediately. If another block needs to react instantly to the change, you may need to proactively update the editor post meta as well.
Option B — Immediate two-way synchronization (recommended for live update across blocks)
To propagate changes instantly to the rest of the editor (other blocks, inspector controls, sidebar UI), update both the block attribute and the post meta in the editor store immediately. Use useEntityProp or dispatch(editPost) to directly modify the posts meta in JavaScript. Use useEffect to reflect changes the other way (when meta changes elsewhere).
Key patterns
- Read post meta with useEntityProp or select(core/editor).getEditedPostAttribute(meta).
- Write post meta with setEntityProp or dispatch(core/editor).editPost({ meta }).
- Update block attributes with setAttributes so the blocks UI updates.
- Use useEffect to keep attribute and meta in sync without infinite loops (detect difference before write).
- Debounce or throttle writes to avoid triggering many autosaves or editor updates.
Full JavaScript edit example using useEntityProp and a text control
This example demonstrates two-way synchronization between a block attribute (subtitle) and post meta key my_plugin_subtitle. It updates meta immediately when the user types and also updates the block attribute if meta is changed elsewhere in the editor.
/ Edit component for subtitle block / import { useEffect, useCallback, useRef } from @wordpress/element import { TextControl } from @wordpress/components import { useBlockProps } from @wordpress/block-editor import { useEntityProp } from @wordpress/core-data export default function Edit( { attributes, setAttributes } ) { const { subtitle } = attributes const [ meta, setMeta ] = useEntityProp( postType, post, meta ) // meta is the post meta object. meta.my_plugin_subtitle is the key we registered in PHP. const initialRef = useRef( true ) // Initialize attribute from meta on first load if attribute is empty. useEffect( () =gt { if ( initialRef.current ) { initialRef.current = false const currentMetaValue = meta meta.my_plugin_subtitle ? meta.my_plugin_subtitle : if ( ! subtitle currentMetaValue ) { setAttributes( { subtitle: currentMetaValue } ) } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [] ) // Keep attribute updated when post meta changes (e.g. changed from other UI) useEffect( () =gt { const metaValue = meta meta.my_plugin_subtitle ? meta.my_plugin_subtitle : if ( metaValue !== subtitle ) { setAttributes( { subtitle: metaValue } ) } }, [ meta, subtitle, setAttributes ] ) // Update both attribute and post meta immediately when user types. const onChange = useCallback( ( value ) =gt { // Update block attribute so this blocks UI updates instantly. setAttributes( { subtitle: value } ) // Update post meta in editor store so other blocks/sidebars see new value. // Because useEntityProp requires setting the whole meta object, merge carefully. const newMeta = Object.assign( {}, meta, { my_plugin_subtitle: value } ) setMeta( newMeta ) }, [ meta, setMeta, setAttributes ] ) return ( ltdiv { ...useBlockProps() }gt ltTextControl label=Subtitle value={ subtitle } onChange={ onChange } /gt lt/divgt ) }
Important notes about the example:
- useEntityProp(postType, post, meta) returns the full meta object and a setter that overwrites the meta object merge existing keys to avoid clobbering unrelated meta.
- Initializing the block attribute from meta prevents empty attribute overriding a preexisting meta value on first load.
- Because setMeta updates the editors post meta state, other editor UI that reads post meta will immediately see the new value (no waiting for manual save).
Alternative using core/editor dispatch
If you prefer not to use useEntityProp, you can write meta using editPost from the core/editor store:
import { useCallback } from @wordpress/element import { useDispatch } from @wordpress/data function updateMetaWithDispatch( key, value ) { const { editPost } = useDispatch( core/editor ) editPost( { meta: { [ key ]: value } } ) // merges in the editor }
Be cautious: using editPost with a bare object merges meta in most WP versions, but using useEntityProp is more explicit and recommended.
Debouncing updates to avoid excessive churn
Immediate setMeta on every keystroke can cause many background editor updates. Use a debounce to limit frequency for expensive flows. The pattern below shows using a simple debounce with useRef and setTimeout.
import { useRef, useEffect } from @wordpress/element function useDebouncedMetaSetter( meta, setMeta, delay = 400 ) { const timerRef = useRef() const setDebouncedMeta = ( nextMeta ) =gt { if ( timerRef.current ) { clearTimeout( timerRef.current ) } timerRef.current = setTimeout( () =gt { setMeta( nextMeta ) timerRef.current = null }, delay ) } // Clear timer on unmount useEffect( () =gt () =gt { if ( timerRef.current ) { clearTimeout( timerRef.current ) } }, [] ) return setDebouncedMeta }
Server-side rendering and retrieving meta in PHP render_callback
If your block is dynamic and rendered server-side, always retrieve the meta on render to keep frontend output consistent.
/ Render callback for dynamic block. @param array attributes Block attributes. / function my_plugin_render_subtitle_block( attributes ) { global post subtitle = get_post_meta( post-gtID, my_plugin_subtitle, true ) if ( empty( subtitle ) isset( attributes[subtitle] ) ) { subtitle = attributes[subtitle] } // Escape and return markup return lth2 class=post-subtitlegt . esc_html( subtitle ) . lt/h2gt } register_block_type( my-plugin/subtitle-block, array( render_callback =gt my_plugin_render_subtitle_block, ) )
Edge cases, pitfalls and troubleshooting
- Meta not visible in editor — Ensure register_post_meta includes show_in_rest =gt true (or proper schema). Also check REST permissions and that your meta key name matches exactly.
- Attribute is empty after first load — If you declare attribute with source: meta, Gutenberg will populate it on load. If using manual synchronization, ensure your edit component reads meta on mount and sets attribute if necessary.
- Infinite update loops — Always compare values before writing. For example, only call setMeta when meta value differs from the requested value, and only call setAttributes when attribute differs.
- Clobbering other meta keys — With useEntityProp you overwrite the whole meta object. Always merge previous meta: setMeta( { …meta, my_key: newValue } ).
- Autosave/revision churn — Frequent writes to post meta cause autosave/revision activity. Debounce meta updates or update meta only on blur/save for less disruptive behavior.
- Type mismatches — Use register_post_meta schema types consistent with your attribute types (string vs. boolean vs. object). JSON encoded objects must be declared as type => object.
Best practices and recommendations
- Prefer register_post_meta with a schema and sanitize callbacks for robust REST behavior and security.
- For simple persist-on-save behavior, prefer block.json attribute mapping (source: meta).
- For cross-block live updates, implement two-way sync using useEntityProp (or core/editor dispatch) plus setAttributes.
- Debounce writes when updating meta on frequent input events.
- Always merge meta objects to avoid removing unrelated meta keys.
- Consider server-rendered blocks when final output must reflect authoritative post meta (use render_callback and get_post_meta in PHP).
Troubleshooting checklist
- Confirm the meta key is registered and show_in_rest is true.
- Open the network inspector and verify the REST response contains the meta field for the post when loading the editor.
- Confirm block.json attribute meta key matches the registered meta key exactly.
- Check console for JavaScript errors from missing imports or incorrect hook usage.
- Check permissions: auth_callback may block visibility to certain user roles.
Advanced topics and variations
- Multiple blocks sharing the same meta key — Use the immediate sync pattern (update meta via setMeta/editPost) so changes in one block immediately affect all other blocks that read meta.
- Meta as object — Register meta with type object and define schema. Carefully update specific properties with setMeta( { …meta, my_object: { …meta.my_object, prop: value } } ).
- Non-post entities — You can register meta for custom post types by replacing the post type slug in register_post_meta and using useEntityProp with the correct postType name.
- Custom REST endpoints — For complex logic or compilation of multiple meta values, consider a custom REST field or route.
Complete minimal working flow recap
- Register meta in PHP with show_in_rest and proper schema.
- Declare attribute in block.json using source: meta for simple save-sync, or declare a normal attribute for UI-only and implement two-way sync in JS.
- In edit component, read meta via useEntityProp (or useSelect) and set attribute when loading.
- When the user changes the UI, call setAttributes for the block and setMeta (or dispatch editPost) to reflect changes in editor post meta immediately.
- Debounce meta writes where appropriate to reduce autosave churn.
- For dynamic PHP rendering, read meta in the render_callback.
Example links and references
- WordPress REST API: Fields and Settings
- register_post_meta() reference
- Block metadata (block.json)
- @wordpress/core-data package
Conclusion
Synchronizing meta fields and block attributes can be implemented either declaratively (block.json mappings) for simple scenarios or with immediate two-way synchronization using useEntityProp / core/editor dispatch for live, cross-block updates. Register meta correctly on the server, merge meta objects safely, debounce writes, and keep attribute updates conditional to avoid infinite loops. With these patterns you can build robust editor experiences that keep post meta and block attributes consistent.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |