Contents
Introduction
This tutorial explains, in exhaustive detail, how to read and write post meta from the Gutenberg block editor using JavaScript. It covers what you must register on the PHP side, how to expose meta to the REST API, how to enqueue editor scripts, and multiple editor-side JavaScript patterns to read and update meta (including hooks-based, data store, and apiFetch examples). It also shows complete, copy-pasteable examples you can adapt for plugins or themes and describes important security, capability and performance considerations.
What meta means in this context
Post meta (custom fields) are arbitrary key/value pairs attached to a post. To interact with meta from the block editor (which runs in JavaScript), you must expose the meta through the REST API so the editor can load and save it. The recommended way is to register meta with show_in_rest => true and relevant type/single settings. Once meta is available through the REST API, JavaScript code inside the editor can read and update it.
High-level workflow
- Register meta in PHP with show_in_rest => true (and optionally sanitization and capability checks).
- Enqueue an editor-only JavaScript file that integrates with the block editor.
- From the editor JS, read and write meta using one of several patterns:
- wp.data dispatch/select (core/editor) — direct post editing
- useEntityProp from @wordpress/core-data — hook-based entity property access
- apiFetch to call the REST endpoint directly
- Provide UI (inspector controls, plugin sidebar, or block controls) so editors can change the meta.
Server-side: register meta correctly (PHP)
You must register the meta key(s) so the REST API includes them. Use register_post_meta (or register_meta) on init and set show_in_rest => true. Also set a type and single where appropriate and provide a sanitize_callback and/or auth_callback if needed for security.
Minimal example (register a single string meta)
lt?php function myplugin_register_meta() { register_post_meta( post, myplugin_meta_key, array( show_in_rest =gt true, single =gt true, type =gt string, sanitize_callback =gt sanitize_text_field, auth_callback =gt function() { return current_user_can( edit_posts ) }, ) ) } add_action( init, myplugin_register_meta ) ?gt
Notes:
- post type: the first argument is the post type (use a custom post type slug if registering meta for custom types).
- single: true means value is a single scalar, false means an array of values.
- type: string, integer, boolean, number, array, object — the REST layer will validate/typedef.
- sanitize_callback: run server-side sanitization to prevent malicious input.
- auth_callback: optionally check capabilities to control who can read/write meta.
Advanced: REST schema and custom REST field name
If you want a different REST field name or a custom schema, you can register a field using register_rest_field or define args to the meta registration. Usually register_post_meta with show_in_rest true suffices.
Enqueue the editor script (PHP)
Enqueue the JavaScript that will run inside the block editor. Use enqueue_block_editor_assets or register a script and use register_block_type with editor_script. Make sure the script lists WordPress packages (handles) it depends on, such as wp-plugins, wp-edit-post, wp-element, wp-components, wp-data, wp-api-fetch, wp-core-data, etc.
Simple enqueue example
lt?php function myplugin_enqueue_editor_assets() { asset_file = plugin_dir_path( __FILE__ ) . build/index.asset.php asset = file_exists( asset_file ) ? require asset_file : array( dependencies =gt array( wp-element, wp-edit-post ), version =gt filemtime( plugin_dir_path( __FILE__ ) . build/index.js ) ) wp_enqueue_script( myplugin-editor-script, plugins_url( build/index.js, __FILE__ ), asset[dependencies], asset[version], true ) } add_action( enqueue_block_editor_assets, myplugin_enqueue_editor_assets ) ?gt
Notes:
- Use build tooling (webpack, @wordpress/scripts) to create index.asset.php automatically, which lists dependencies and version.
- Enqueue only when in the editor: enqueue_block_editor_assets fires only for editor pages.
Editor-side: patterns to read and write meta in JavaScript
There are several patterns you can use. Choose based on whether you want to be idiomatic React/hooks (useEntityProp or useSelect/useDispatch) or imperative (wp.data.dispatch or apiFetch).
1) Using wp.data.select and wp.data.dispatch (core/editor)
This is the simplest imperative approach. Use select(core/editor).getEditedPostAttribute(meta) to read current meta and dispatch(core/editor).editPost({ meta: {…} }) to update it. This approach simply edits the current editor post object Gutenberg manages the rest.
/ index.js A minimal Plugin with a text input updating meta using wp.data.dispatch / ( function ( wp ) { const { registerPlugin } = wp.plugins const { PluginDocumentSettingPanel } = wp.editPost const { TextControl } = wp.components const { createElement } = wp.element const { select, dispatch } = wp.data const MyMetaPanel = () =gt { const meta = select( core/editor ).getEditedPostAttribute( meta ) const value = meta meta.myplugin_meta_key ? meta.myplugin_meta_key : return createElement( PluginDocumentSettingPanel, { name: myplugin-meta-panel, title: My Meta, }, createElement( TextControl, { label: My Meta Key, value: value, onChange: ( newVal ) =gt { // Merge other meta keys to avoid overwriting const currentMeta = select( core/editor ).getEditedPostAttribute( meta ) {} dispatch( core/editor ).editPost( { meta: { ...currentMeta, myplugin_meta_key: newVal } } ) }, } ) ) } registerPlugin( myplugin-meta-plugin, { render: MyMetaPanel } ) } )( window.wp )
Notes and pitfalls:
- When using editPost to update meta, pass the full meta object you want to persist (merge other meta fields to avoid unintentionally clearing them).
- Because the editor manages the post edit state, changes will be saved when the editor saves the post (no separate manual REST call required).
2) Using the React hooks from @wordpress/core-data: useEntityProp
useEntityProp is the idiomatic hook to read and set an entity property (like post meta). This gives you a React state-like pair [meta, setMeta] for the entity. It is concise and recommended for new code.
Example: hook-based functional component using useEntityProp
/ index.js Example: useEntityProp to get/set meta / import { registerPlugin } from @wordpress/plugins import { PluginDocumentSettingPanel } from @wordpress/edit-post import { TextControl } from @wordpress/components import { useEntityProp } from @wordpress/core-data import { Fragment } from @wordpress/element const MyMetaPanel = () =gt { // For posts: kind = postType, name = post, key = meta const [ meta, setMeta ] = useEntityProp( postType, post, meta ) const value = ( meta meta.myplugin_meta_key ) ? meta.myplugin_meta_key : return ( ltPluginDocumentSettingPanel name=myplugin-meta-panel title=My Meta gt ltTextControl label=My Meta Key value={ value } onChange={ ( newVal ) =gt setMeta( { ...meta, myplugin_meta_key: newVal } ) } /gt lt/PluginDocumentSettingPanelgt ) } registerPlugin( myplugin-meta-plugin, { render: MyMetaPanel } )
Important details:
- useEntityProp arguments: kind (usually postType), name (post type slug, usually post for standard posts), and key (meta).
- setMeta replaces the meta value therefore merge existing keys if you want to keep them.
- useEntityProp will be reactive — components will re-render when meta updates.
3) Using apiFetch to call the REST endpoint explicitly
If you need to send a REST request yourself (for example, to edit a post programmatically or to update meta outside the core/editor state), use wp.apiFetch. This approach bypasses the editors local store it directly calls the REST API. Use it carefully to avoid race conditions with the editors autosave.
Example: using apiFetch to update meta
/ Update meta via REST using apiFetch / import apiFetch from @wordpress/api-fetch const updateMetaViaREST = async ( postId, metaKey, value ) =gt { const response = await apiFetch( { path: /wp/v2/posts/{postId}, method: POST, data: { meta: { [ metaKey ]: value, }, }, } ) return response } // Usage example updateMetaViaREST( 123, myplugin_meta_key, new value ) .then( ( res ) =gt console.log( Updated, res ) ) .catch( ( err ) =gt console.error( err ) )
When to use apiFetch:
- When youre building a custom UI that updates the server immediately and you dont need to rely on the editors edit-state.
- Be aware of concurrency with Gutenberg autosaves and the local edited post state. Its generally safer to use the editors store if working inside the editor UI.
4) Using selectors for debugging and read-only uses
To inspect meta in the console or to build read-only UI elements, use the core/editor selectors:
// Read the current edited post meta from console const meta = wp.data.select( core/editor ).getEditedPostAttribute( meta ) console.log( meta ) // Read post ID const postId = wp.data.select( core/editor ).getEditedPostId() console.log( postId )
Full end-to-end example (plugin)
Below is a compact end-to-end example showing the PHP plugin bootstrap and a React-style editor script (using built packages). It covers registration, enqueueing, and a plugin that uses useEntityProp to edit meta.
PHP plugin bootstrap (single-file minimal plugin)
lt?php / Plugin Name: MyPlugin Meta Editor Description: Example plugin exposing post meta to Gutenberg and adding an editor panel. Version: 1.0 Author: You / if ( ! defined( ABSPATH ) ) { exit } function myplugin_register_meta() { register_post_meta( post, myplugin_meta_key, array( show_in_rest =gt true, single =gt true, type =gt string, sanitize_callback =gt sanitize_text_field, auth_callback =gt function() { return current_user_can( edit_posts ) }, ) ) } add_action( init, myplugin_register_meta ) function myplugin_enqueue_editor_assets() { script_path = plugin_dir_path( __FILE__ ) . build/index.js if ( file_exists( script_path ) ) { asset_file = plugin_dir_path( __FILE__ ) . build/index.asset.php asset = file_exists( asset_file ) ? require asset_file : array( dependencies =gt array( wp-element, wp-edit-post ), version =gt filemtime( script_path ) ) wp_enqueue_script( myplugin-editor-script, plugins_url( build/index.js, __FILE__ ), asset[dependencies], asset[version], true ) } } add_action( enqueue_block_editor_assets, myplugin_enqueue_editor_assets ) ?gt
Editor JS (React/ESNext) – uses useEntityProp
/ build/index.js (after bundling) Example using @wordpress/ packages and modern JSX. / import { registerPlugin } from @wordpress/plugins import { PluginDocumentSettingPanel } from @wordpress/edit-post import { TextControl } from @wordpress/components import { useEntityProp } from @wordpress/core-data const MyMetaPanel = () =gt { const [ meta, setMeta ] = useEntityProp( postType, post, meta ) const value = ( meta meta.myplugin_meta_key ) ? meta.myplugin_meta_key : return ( ltPluginDocumentSettingPanel name=myplugin-meta-panel title=My Meta gt ltTextControl label=My Meta Key value={ value } onChange={ ( newVal ) =gt { setMeta( { ...meta, myplugin_meta_key: newVal } ) } } /gt lt/PluginDocumentSettingPanelgt ) } registerPlugin( myplugin-meta-plugin, { render: MyMetaPanel } )
Best practices and important details
- Always register meta server-side with show_in_rest => true. Without this, the meta wont appear in the post object in the REST responses and the editor cant edit it reliably.
- Use sanitize_callback to ensure server-side sanitization of meta inputs.
- Use auth_callback to restrict who can read/write meta fields if needed (for example, if the meta should only be edited by admins).
- Types matter: registering the correct type helps the REST API validate and coerce values.
- Merging meta: when using dispatch(core/editor).editPost or setMeta from useEntityProp, merge other meta keys to avoid removing them. Example: setMeta({ …meta, key: newValue }).
- Autosave interactions: if you use apiFetch to update meta while the user is editing in the editor, the editors local state may not reflect that immediate change — this can cause unexpected results. Prefer using the editor store for editor UIs.
- Data store debugging: you can inspect editor state with wp.data.select(core/editor).getEditedPostAttribute(meta) in the browser console to validate what the editor currently plans to save.
- REST performance: avoid sending large payloads frequently. Use debouncing if you update meta on each keystroke via REST.
Common pitfalls and how to avoid them
- Meta not available in editor: forgot to register_post_meta with show_in_rest true. Fix: register meta on init.
- Meta exists server-side but is not saved or is overwritten: when updating meta, you must merge existing meta fields to avoid clearing them. Use current meta as the base before setting a changed key.
- Race conditions with apiFetch: avoid updating the server while Gutenberg autosaves or updates the post edit state. Use the editor store pattern instead.
- Incorrect useEntityProp arguments: useEntityProp( postType, post, meta ) for posts. For custom post types use the custom post type slug as the second argument.
Debugging checklist
- Confirm register_post_meta is called on init and show_in_rest is true.
- Open the network tab and inspect the REST response for the post (GET /wp/v2/posts/{id}) to see if meta is included.
- In the editor console run:
const meta = wp.data.select(core/editor).getEditedPostAttribute(meta) console.log(meta)
- If setMeta or editPost doesnt persist: check user capabilities (auth_callback) and sanitize callbacks that may strip values.
- Ensure your editor script dependencies include wp-data, wp-core-data, wp-api-fetch, etc., as required by your code.
Summary
To write meta from the block editor using JavaScript: register the meta on the server with show_in_rest => true, enqueue an editor script, and then use one of the JavaScript patterns to read and update that meta. The most idiomatic options inside the editor are useEntityProp (hook-based) or wp.data.select/dispatch (imperative). Use apiFetch for direct REST calls when you need them, but be mindful of autosave and store state. Always sanitize and protect meta with proper server-side validation and capability checks.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |