The WordPress REST API exposes a set of default fields for every post — id, title, content, status, and so on. When your theme or plugin stores additional data in post meta, taxonomies, or custom tables, you can surface that data in the API response without building a custom endpoint by using register_rest_field().
Problem: The WordPress REST API response for a post type is missing custom field values — such as ACF data or post meta — that a JavaScript front end or mobile app needs.
Solution: Call register_rest_field() inside a rest_api_init hook, specifying the post type, the field name as it should appear in the response, and a get_callback that returns the value. The field then appears at the top level of every REST API response for that object type.
register_rest_field() accepts three arguments: the post type (or array of types), the field name that will appear in the JSON, and an array of callbacks — one to read the value (get_callback), one to write it (update_callback), and an optional schema definition.
Example 1 — expose post meta in the REST response:
<?php
add_action( 'rest_api_init', 'register_post_meta_rest_field' );
function register_post_meta_rest_field() {
register_rest_field(
'post', // post type
'reading_time', // key name in the JSON response
[
'get_callback' => 'get_reading_time_meta',
'update_callback' => 'update_reading_time_meta',
'schema' => [
'description' => 'Estimated reading time in minutes.',
'type' => 'integer',
'context' => [ 'view', 'edit' ],
],
]
);
}
function get_reading_time_meta( $post ) {
return (int) get_post_meta( $post['id'], '_reading_time', true );
}
function update_reading_time_meta( $value, $post ) {
update_post_meta( $post->ID, '_reading_time', (int) $value );
}
A GET request to /wp-json/wp/v2/posts/123 will now include "reading_time": 5 in the response. A PATCH request with a JSON body of {"reading_time": 8} will update the meta value.
Example 2 — add the field to a custom post type:
<?php
add_action( 'rest_api_init', 'register_product_sku_field' );
function register_product_sku_field() {
register_rest_field(
'product', // custom post type slug
'sku',
[
'get_callback' => function ( $post ) {
return get_post_meta( $post['id'], '_sku', true );
},
'schema' => [
'type' => 'string',
'context' => [ 'view' ],
],
]
);
}
Example 3 — expose taxonomy terms as a flat array:
<?php
add_action( 'rest_api_init', 'register_tag_names_field' );
function register_tag_names_field() {
register_rest_field(
'post',
'tag_names',
[
'get_callback' => function ( $post ) {
$terms = get_the_terms( $post['id'], 'post_tag' );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return [];
}
return wp_list_pluck( $terms, 'name' );
},
'schema' => [
'type' => 'array',
'items' => [ 'type' => 'string' ],
],
]
);
}
This is especially convenient when building a decoupled front end (React, Vue, etc.) that needs clean, flat data structures rather than navigating nested term objects.
NOTE: register_rest_field() adds the field to all REST contexts by default. If the field contains sensitive data, restrict the context in the schema to 'edit' only, and make sure the get_callback checks current_user_can() before returning privileged information.