By default, ACF fields are not included in WordPress REST API responses. The standard /wp-json/wp/v2/posts endpoint returns core post fields only. Getting ACF data into the REST API requires explicit registration – which also gives you control over exactly what is exposed and to whom.
The Simple Way: register_rest_field()
Register individual ACF fields for REST API inclusion using register_rest_field(). Add this to functions.php or a Code Snippets snippet:
add_action('rest_api_init', function() {{
register_rest_field(
'post', // post type, or array of post types
'bedrooms', // the key name in the REST response
array(
'get_callback' => function($post) {{
return get_field('bedrooms', $post['id']);
}},
'schema' => array(
'type' => 'integer',
'description' => 'Number of bedrooms',
),
)
);
}});
The field now appears in /wp-json/wp/v2/posts as a top-level key. The get_callback receives the post array and should return the processed value – get_field() handles ACF’s value formatting (returning arrays for relationship fields, formatted dates for date fields, etc.).
Exposing All ACF Fields at Once
For development or internal APIs where you want all fields without registering each individually:
add_action('rest_api_init', function() {{
$post_types = array('post', 'page', 'property');
foreach ($post_types as $post_type) {{
register_rest_field(
$post_type,
'acf',
array(
'get_callback' => function($post) {{
return get_fields($post['id']);
}},
)
);
}}
}});
This adds an “acf” key to the REST response containing all fields for that post. This is convenient but exposes everything – use individual field registration for public APIs where you want to control what is returned.
Need this built properly? Describe the project and get a free estimate.
Restricting Fields to Authenticated Users
Some ACF fields contain data that should not be publicly accessible. Add an authentication check in the get_callback:
register_rest_field(
'customer',
'internal_notes',
array(
'get_callback' => function($post) {{
// Only expose to logged-in users with edit capability
if (!current_user_can('edit_posts')) {{
return null;
}}
return get_field('internal_notes', $post['id']);
}},
)
);
Writing ACF Fields via REST API
To allow updating ACF fields through PUT/PATCH requests, add an update_callback:
register_rest_field(
'property',
'price',
array(
'get_callback' => function($post) {{
return get_field('price', $post['id']);
}},
'update_callback' => function($value, $post) {{
update_field('price', absint($value), $post->ID);
}},
'schema' => array(
'type' => 'integer',
),
)
);
The update_callback runs when a REST API PUT or PATCH request includes the field. Always validate and sanitise $value before saving – REST API requests can come from external systems.
ACF in Headless WordPress
For headless setups where a Next.js, Nuxt, or other frontend fetches WordPress content via REST API, the register_rest_field approach works but has a limitation: you need to register every field you want available. ACF Pro’s built-in REST API support (Settings -> ACF -> REST API) exposes all fields automatically with less code, but gives you less control over the output format.
A common headless pattern is to expose a custom endpoint that returns exactly the fields your frontend needs:
add_action('rest_api_init', function() {{
register_rest_route('myapp/v1', '/properties', array(
'methods' => 'GET',
'callback' => 'myapp_get_properties',
'permission_callback' => '__return_true',
));
}});
function myapp_get_properties($request) {{
$posts = get_posts(array(
'post_type' => 'property',
'posts_per_page' => 20,
'meta_key' => 'price',
'orderby' => 'meta_value_num',
));
return array_map(function($post) {{
return array(
'id' => $post->ID,
'title' => get_the_title($post),
'price' => get_field('price', $post->ID),
'bedrooms' => get_field('bedrooms', $post->ID),
'images' => get_field('gallery', $post->ID),
);
}}, $posts);
}}