Gutenberg’s useEntityProp hook (from @wordpress/core-data) is the canonical way for block edit components to read and write post attributes — specifically post meta — that are stored in the WordPress database. Before useEntityProp, blocks used wp_localize_script() to pass meta values into the editor and useDispatch( 'core/editor' ).editPost() to write them back. useEntityProp replaces this two-step pattern with a single hook that returns a tuple of [value, setValue, fullObject], similar to React’s useState. When the value is updated via setValue, it is written to the entity store and the editor marks the post as having unsaved changes — persisting it to the REST API when the post is saved. The meta field must be registered with register_post_meta() with 'show_in_rest' => true for the hook to work.
Problem: A custom block needs to read and write a post meta field _featured_quote directly from the block editor sidebar, without a separate metabox. The value should update in real time when the user types, be part of the normal save flow, and be read server-side in a PHP render callback.
Solution: Register the meta with register_post_meta() exposing it to the REST API. Use useEntityProp('postType', postType, 'meta') in the block edit component to read and update the meta value.
<?php
// ── 1. Register the meta field with REST API exposure ─────────────────
add_action( 'init', function () {
register_post_meta( 'post', '_featured_quote', [
'type' => 'string',
'single' => true,
'default' => '',
'show_in_rest' => true, // REQUIRED for useEntityProp to work
'auth_callback' => function () {
return current_user_can( 'edit_posts' );
},
'sanitize_callback' => 'sanitize_textarea_field',
] );
} );
// ── 2. Render callback reads the meta server-side ─────────────────────
function render_featured_quote_block( array $attributes, string $content, WP_Block $block ): string {
$post_id = $block->context['postId'] ?? get_the_ID();
$quote = get_post_meta( $post_id, '_featured_quote', true );
if ( ! $quote ) return '';
return sprintf(
'<blockquote class="featured-quote"><p>%s</p></blockquote>',
esc_html( $quote )
);
}
// edit.js
import { useEntityProp } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextareaControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit() {
const blockProps = useBlockProps();
// Get the current post type (blocks can be used in multiple post types)
const postType = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostType(),
[]
);
// useEntityProp returns [ value, setValue, fullEntityObject ]
// 'postType' = entity kind, postType = entity name, 'meta' = property key
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const quote = meta?._featured_quote ?? '';
const setQuote = ( val ) => setMeta( { ...meta, _featured_quote: val } );
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Featured Quote', 'textdomain' ) }>
<TextareaControl
label={ __( 'Quote text', 'textdomain' ) }
value={ quote }
onChange={ setQuote }
help={ __( 'Displayed as a styled blockquote.', 'textdomain' ) }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{ quote
? <blockquote className="featured-quote"><p>{ quote }</p></blockquote>
: <p className="placeholder">{ __( 'Enter a featured quote in the sidebar →', 'textdomain' ) }</p>
}
</div>
</>
);
}
NOTE: The meta key passed to useEntityProp via the meta property returns an object containing all registered meta fields for the post — access individual fields as meta._featured_quote. When calling setMeta(), always spread the full meta object and override only the key you want to change: setMeta({ ...meta, _featured_quote: val }) — overwriting the entire meta object with a single key would clear all other meta values. Meta fields with names starting with _ (underscore prefix) are "protected" in WordPress — they require a registered auth_callback in register_post_meta() and are hidden from the standard Custom Fields metabox. Without 'show_in_rest' => true in the register_post_meta() call, the field will not appear in the REST API response and useEntityProp will return undefined.