Skip to content

REST API, Meta: Allow leaving out meta keys without a stored value - #14831

Open
DevinWalker wants to merge 2 commits into
WordPress:trunkfrom
DevinWalker:48478-rest-meta-omit-if-not-exists
Open

DevinWalker wants to merge 2 commits into
WordPress:trunkfrom
DevinWalker:48478-rest-meta-omit-if-not-exists

Conversation

@DevinWalker

@DevinWalker DevinWalker commented Oct 24, 2026 •

Copy link
Copy Markdown

Registered meta that's shown in the REST API is always included in responses, using its default when nothing is stored. A client that saves a response back then stores that default for keys nobody changed. The block editor does this whenever any meta key is edited, since it sends back the whole meta object (WordPress/gutenberg#17864).

This adds the option @TimothyBJacobs suggested on the ticket: an omit_if_not_exists key for the show_in_rest argument of register_meta().

register_post_meta( 'post', 'my_meta', array(
	'show_in_rest' => array( 'omit_if_not_exists' => true ),
	'single'       => true,
	'type'         => 'string',
) );

When it's true, WP_REST_Meta_Fields::get_value() leaves the key out of the response until a value is stored. It reads meta with get_metadata_raw(), which returns null when nothing is stored, and only falls back to the registered default for keys without the option. Once a value is stored, even an empty string, the key shows up as usual.

  • It's opt-in, so responses for existing keys don't change.
  • The schema still lists the key, so clients can keep setting it.
  • It's in WP_REST_Meta_Fields, so it works for post, term, comment and user meta.

Trac ticket: https://core.trac.wordpress.org/ticket/48478

Testing

New tests in tests/phpunit/tests/rest-api/rest-post-meta-fields.php (--group 48478, 9 cases). Without the change, the 5 that cover leaving keys out fail. The other 4 check that stored values (an empty string and multiple values too) and the schema work as before. The 3 cases for leaving keys out also check that a key without the option is still included.

trunk this PR
--group 48478, single site 5 failures 9/9 pass
--group 48478, multisite 9/9 pass
Post, term, user and comment meta fields tests 212/212 pass
--group restapi 3875 tests, no failures
--group meta 505/505 pass

The restapi run has the same 4 warnings as trunk (test_get_items_wdotorg_unavailable, test_create_item_wdotorg_unreachable and the two test_format_validation_is_applied_if_missing_type tests).

PHPCS and PHPStan are clean on the changed lines.

I also checked it in the block editor on a local wordpress-develop site, with two regular keys and one using omit_if_not_exists. After editing one of the regular keys and saving a draft:

trunk this PR
Edited key stored 'Changed' stored 'Changed'
Untouched regular key stored '' stored ''
Untouched key with omit_if_not_exists stored '' not stored

Manual testing

  1. Add this as an mu-plugin:
    <?php
    add_action( 'init', function () {
    	foreach ( array( 'demo_edited', 'demo_regular' ) as $key ) {
    		register_post_meta( 'post', $key, array( 'show_in_rest' => true, 'single' => true, 'type' => 'string' ) );
    	}
    	register_post_meta( 'post', 'demo_omitted', array(
    		'show_in_rest' => array( 'omit_if_not_exists' => true ),
    		'single'       => true,
    		'type'         => 'string',
    	) );
    } );
  2. Go to "Posts > Add New Post".
  3. In the browser console, run wp.data.dispatch( 'core/editor' ).editPost( { meta: { demo_edited: 'Changed' } } );
  4. Click "Save draft", then run npm run env:cli -- post meta list <ID>.
    • Before: demo_omitted is stored with an empty value.
    • After: demo_omitted isn't stored. demo_edited and demo_regular are stored as before.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Writing the change and the PHPUnit tests, the block editor check, and drafting this description. I reviewed the code and the test results before opening this PR.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Registered meta shown in the REST API is always included in responses, with its default value when nothing is stored. A client that saves a response back, like the block editor does, then stores that default for keys nobody changed.

Adds an `omit_if_not_exists` key to the `show_in_rest` argument of `register_meta()`. When it's true, `WP_REST_Meta_Fields::get_value()` leaves the key out of responses until a value is stored. The schema still describes the key, so clients can keep setting it.

See #48478.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props dlocc.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

`WP_REST_Meta_Fields::get_value()` now reads meta with `get_metadata_raw()` and falls back to `get_metadata_default()`, the same steps `get_metadata()` takes. Keys with `omit_if_not_exists` no longer need a separate `metadata_exists()` call, and a filtered value that is falsy is no longer treated as missing.

The tests now use data providers, the class's `test_single` key and its `grant_write_permission()` helper.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant