WordPress register_post_meta Example: Expose Custom Fields to REST Safely
Register custom post meta safely for REST API and block editor workflows with schema, sanitization, auth callbacks, and testing steps.
Published
April 19, 2026
Reading Time
4 min read
Updated
April 19, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 19, 2026.
Full Report
Last reviewed: April 19, 2026
Custom fields are one of the most useful parts of WordPress, but they become messy when they are not registered properly. In headless projects and block editor workflows, unregistered meta often leads to missing REST fields, unsafe values, inconsistent schemas, and frontend code that depends on private implementation details.
This guide shows a practical register_post_meta() example for exposing a custom field to the WordPress REST API safely. The same pattern works for normal posts and custom post types when the field is part of the public content model.
Who this guide is for
This article is for WordPress developers building custom fields for headless frontends, block editor components, custom post types, editorial metadata, or API-driven publishing workflows.
Why registered meta matters
Registering post meta gives WordPress a schema for the field. That schema tells WordPress the field type, whether it is single or multi-value, how to sanitize input, whether it appears in REST responses, and who can read or edit it. Without that contract, custom fields are easy to misuse.
Example: expose reading time to REST
This plugin registers a vulnwp_reading_time integer field for posts. It is visible in the REST API, sanitized as a positive integer, and protected by an authorization callback.
<?php
/**
* Plugin Name: VulnWP Reading Time Meta
* Description: Registers a REST-visible reading time field for posts.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'init', 'vulnwp_register_reading_time_meta' );
function vulnwp_register_reading_time_meta() {
register_post_meta(
'post',
'vulnwp_reading_time',
array(
'type' => 'integer',
'description' => __( 'Estimated reading time in minutes.', 'vulnwp-meta' ),
'single' => true,
'default' => 0,
'show_in_rest' => true,
'sanitize_callback' => 'absint',
'auth_callback' => 'vulnwp_can_access_reading_time_meta',
)
);
}
function vulnwp_can_access_reading_time_meta( $allowed, $meta_key, $post_id, $user_id, $cap, $caps ) {
if ( 'edit_post_meta' === $cap || 'delete_post_meta' === $cap ) {
return current_user_can( 'edit_post', $post_id );
}
return true;
}
The field appears inside the meta object of REST post responses when requested. For custom post types, make sure the post type supports custom fields and is visible in REST if it needs editor or API integration.
Test the REST response
curl -s 'https://example.com/wp-json/wp/v2/posts/123?_fields=id,slug,meta' | jq
A successful response should include a structure like this:
{
"id": 123,
"slug": "example-post",
"meta": {
"vulnwp_reading_time": 7
}
}
Save meta from PHP
When updating registered meta from PHP, still validate the source of the value. Registration is not a replacement for request-level checks.
function vulnwp_update_reading_time( $post_id, $minutes ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return false;
}
return update_post_meta(
$post_id,
'vulnwp_reading_time',
absint( $minutes )
);
}
Public fields vs private fields
Do not expose everything to REST just because a frontend can read it. Public editorial metadata like reading time, difficulty level, canonical source, product SKU, or documentation version may be fine. Secrets, internal notes, API keys, cost data, private user decisions, and moderation signals should not be exposed publicly.
If a field is needed only by authenticated editors, keep it protected and enforce permissions through auth_callback and the surrounding REST route or editor flow.
Field naming rules
Choose a meta key that is stable and specific. A generic key like reading_time may collide with another plugin or theme. A prefixed key such as vulnwp_reading_time is easier to audit and safer to expose to REST. Once a frontend depends on the key, changing it becomes a migration.
Private meta keys often start with an underscore. That can be useful for hiding fields from the classic Custom Fields UI, but do not assume the underscore alone is a security boundary. Visibility, editing, and REST behavior should be controlled by registration arguments and permissions.
Array and object fields
Scalar fields are easiest to register. If the meta value is an array or object and should appear in REST, define the schema explicitly. Otherwise WordPress cannot reliably validate the shape of the field.
register_post_meta(
'post',
'vulnwp_source_links',
array(
'type' => 'array',
'single' => true,
'show_in_rest' => array(
'schema' => array(
'type' => 'array',
'items' => array(
'type' => 'string',
),
),
),
)
);
Production checklist
- Register meta on
init. - Use a stable, namespaced meta key.
- Set
type,single,default, anddescription. - Use a sanitization callback that matches the field type.
- Expose only fields that should be available to the intended REST audience.
- For custom post types, confirm REST and custom field support.
- Document whether the field is editorial, operational, private, or public.
Testing workflow
- Register the field in a plugin and activate it on staging.
- Update a post with a test value from PHP, the editor, or a REST request.
- Request the post with
_fields=id,slug,metaand confirm the value shape. - Test invalid values and confirm sanitization behaves as expected.
- Test with a low-privilege user and confirm edit attempts are blocked.
Do not test only the happy path. Meta fields often break when values are empty, missing, typed incorrectly, or edited by users with fewer capabilities than administrators.
Common mistakes
- Using generic meta keys. Prefix custom keys to avoid collisions.
- Skipping
auth_callback. Access rules should be explicit. - Exposing protected data. REST responses can become public content surfaces.
- Changing field type later. Frontends and block editor code may depend on the schema.
- Forgetting custom post type support. Meta may not behave as expected without the right post type configuration.
Related reading
This builds on the custom post type example and pairs well with the block.json custom block example when a block needs to read or write post metadata.


