Technote

Development workflow Intro

Series The REST API and going headless Part 2 of 8

Exposing a post type and its meta with show_in_rest

A post type opens with one line. Meta does not: it needs a registered type, an auth_callback governs writing, and reading opens regardless.

If your custom post type is missing from /wp-json/wp/v2/, there is one reason: show_in_rest was never set. That single line does more than create a route — it is also the switch that turns on the block editor, because the editor runs on REST. Leave it false and editing falls back to the classic editor.

The post type — one line, plus what comes with it

register_post_type(
	'wper_tool',
	[
		'public'       => true,
		'show_in_rest' => true,     // route + block editor
		'rest_base'    => 'tools',  // otherwise the route uses the raw post type key
		'supports'     => [ 'title', 'editor', 'custom-fields' ],
	]
);

Without rest_base the address becomes /wp-json/wp/v2/wper_tool. That works, but it publishes your internal prefix as part of a public API, and the day you change the prefix every client breaks with it. Decide the public name separately, from the start.

Taxonomies follow the same pattern: give register_taxonomy() a show_in_rest and a rest_base, and you get term routes plus that taxonomy as a field on post responses.

The last line of that example matters more than it looks. If the post type does not support custom-fields, the response schema never gets a meta property at all. You can register meta perfectly and see nothing, and because the cause is not in the meta registration you will look in the wrong place for a while.

Meta takes more than one line

To put meta on the API you must register it with register_post_meta(). An unregistered key never appears, however much data sits in it. Registration also needs a type, because REST builds a schema first and validates values against it — with no type there is nowhere to put the value.

register_post_meta(
	'wper_tool',
	'github_url',
	[
		'type'          => 'string',
		'single'        => true,     // omit this and REST returns an array
		'show_in_rest'  => true,
		'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
			return current_user_can( 'edit_post', $post_id );
		},
	]
);

Forgetting single is the most common slip. It defaults to false, so the field comes back as "github_url": ["https://…"]an array where the client expected a string — and the client quietly renders nothing.

For arrays and objects, pass [ 'schema' => [ … ] ] instead of true so the item shape is declared. Registration refuses a complex type with no schema, which is the right default: it declines to publish a value the API has no way to validate.

Reading and writing use different doors

This is where accidents happen. auth_callback gates writing. Reading is not affected by it at all — anyone who can read the post gets the meta with it.

How meta exposure really works — reads and writes are decided separately

Protected keys beginning with an underscore default to write denied when no auth_callback is given, which produces the symptom “the value saves but the editor cannot change it”. Conversely, the moment you set show_in_rest that value becomes publicly readable, so do not put internal data on the API merely because you would like to edit it in the block editor.

The test is simple: would you be comfortable if this value were printed in the page source? If not, handle it with a meta box and admin_post instead. Payment credentials and server access details stay well away from the API for exactly this reason.

How we decide on structured field design is covered further in the Themes & plugins archive, and the procedure for rolling a data layer like this onto a live site is on our process page.

Next part

Sooner or later core routes are not enough. Next: your own endpoints — choosing a namespace, why permission_callback is a required argument, and how an args schema saves you from hand-writing validation.

More on this topic

All technotes

Development workflow Practical

Turning taste arguments into rule checks

"It feels a bit cramped" can be neither argued with nor fixed. Spacing off the scale, colour off the palette, contrast below threshold, missing states — four rules…

Designers 9 min read

Development workflow Practical

Do not swap everything at once

A full swap makes every problem appear at the same moment — which means none of them can be attributed. So you switch one template at a time.

Designers 6 min read

Development workflow Practical

Adding an SCSS build, and whether to commit the output

WordPress themes are expected to deploy without a build step, which leads to the opposite conclusion from ordinary application code — and to its own costs.

Developers 7 min read

₩270,000 · Join the program