By default, WordPress custom post types are not exposed to the REST API — they do not appear at /wp-json/wp/v2/ and their posts cannot be fetched, created, or updated by REST clients. The show_in_rest argument on register_post_type() opts the CPT into the REST API, and a matching show_in_rest argument on register_meta() or register_post_meta() makes custom fields available in the meta object of each post response. This combination powers the block editor — Gutenberg requires show_in_rest => true to enable the block editor interface for a custom post type, and individual meta fields must be registered with show_in_rest to be readable and writable in the editor sidebar. Getting this configuration right is necessary for any CPT that needs a block editor experience or a JavaScript/mobile client.
Problem: A custom post type event was registered without show_in_rest. The block editor is not available for events, and the REST API endpoint /wp-json/wp/v2/events does not exist. Custom meta fields _event_date and _event_location need to be readable in REST responses.
Solution: Add 'show_in_rest' => true to the CPT registration arguments, and call register_post_meta() for each meta field that should appear in REST responses — with 'show_in_rest' => true and a 'sanitize_callback'.
<?php
// ── CPT with block editor and REST API support ────────────────────────
add_action( 'init', function () {
register_post_type( 'event', [
'label' => 'Events',
'public' => true,
'show_in_rest' => true, // enables REST API + block editor
'rest_base' => 'events', // → /wp-json/wp/v2/events (default: post type slug)
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ],
// 'custom-fields' in 'supports' is required for meta to appear in block editor sidebar
'rewrite' => [ 'slug' => 'events' ],
] );
} );
// ── Register meta fields for REST API + block editor ──────────────────
add_action( 'init', function () {
register_post_meta( 'event', '_event_date', [
'type' => 'string',
'description' => 'Event date in Y-m-d format',
'single' => true,
'sanitize_callback' => function ( $value ) {
// Validate Y-m-d format
$dt = DateTime::createFromFormat( 'Y-m-d', $value );
return ( $dt && $dt->format( 'Y-m-d' ) === $value ) ? $value : '';
},
'auth_callback' => fn() => current_user_can( 'edit_posts' ),
'show_in_rest' => true, // included in REST responses + editable via REST
] );
register_post_meta( 'event', '_event_location', [
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => fn() => current_user_can( 'edit_posts' ),
'show_in_rest' => true,
] );
// Meta with a complex REST schema (e.g. an object/array value)
register_post_meta( 'event', '_event_speakers', [
'type' => 'array',
'single' => true,
'show_in_rest' => [
'schema' => [
'type' => 'array',
'items' => [ 'type' => 'string' ],
],
],
] );
} );
// ── Reading from the REST API ──────────────────────────────────────────
// GET /wp-json/wp/v2/events?_fields=id,title,meta
// Response: { "id": 42, "title": { "rendered": "..." }, "meta": { "_event_date": "2021-06-15" } }
// ── Writing via REST API ───────────────────────────────────────────────
// POST /wp-json/wp/v2/events/42 with body: { "meta": { "_event_date": "2021-07-01" } }
// Requires valid authentication + auth_callback returning true
// ── Reading in the block editor via useEntityProp ──────────────────────
/*
import { useEntityProp } from '@wordpress/core-data';
const [ meta, setMeta ] = useEntityProp( 'postType', 'event', 'meta' );
const eventDate = meta['_event_date'];
const setEventDate = (val) => setMeta({ ...meta, '_event_date': val });
*/
NOTE: Meta fields with names starting with an underscore (_event_date) are treated as "protected" by WordPress — they are hidden from the custom fields meta box in the classic editor. In the block editor they are still accessible via useEntityProp when registered with show_in_rest => true. Also, 'custom-fields' must be listed in the CPT's 'supports' array for the block editor's custom fields panel to be available — this is a separate requirement from show_in_rest. Without it, registered meta fields are exposed in REST API responses but not editable in the sidebar panel.