Completing the implementation of metadata registration with the REST API

A priority of the REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) team for the upcoming 5.0 release is to finally enhance WordPress metadata registration so that it can cover common APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. use-cases. This will help with adaptation of the REST API by making management of custom metadata in existing content endpoints easier, which should in turn also benefit any metadata-related work in GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. This post provides some insight into the surrounding issues, shows the current progress of the improvements, and highlights what still needs to be discussed.

A little history on register_meta()

The register_meta() function has been around since 3.3, but did not attract much attention with developers until WordPress 4.6 introduced enhancements to permit metadata registration for use within the REST API. As with other metadata functions, an object type (by coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. definitions either a “post”, “term”, “comment”, or “user”) needs to be passed to the function, alongside the metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. key to register and the arguments for its behavior.

However, metadata in WordPress is often used only for a subset of one of the above four types. When dealing with post meta in a pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the Plugin Directory or can be cost-based plugin from a third-party for example, that post metadata commonly only applies to one or more specific post types – not to all posts of any post type. That is what currently makes register_meta( $object_type, $meta_key, $args ) unusable for the majority of REST API-related cases. Before the enhanced version was introduced in 4.6 many thoughts were put into this concern, with a subtype-aware implementation even being merged into core. That implementation was reverted before the release because it was unclear how to handle conflicts (more on that below). The ticketticket Created for both bug reports and feature development on the bug tracker. where all those changes were discussed is #35658.

Misconceptions about register_meta()

The improvements and their documentation caused developers to listen up, but unfortunately since the concept of an object type is rarely exposed in high-level WordPress APIs, many people misunderstood the first parameter as requiring a post type because post meta is the most common kind of metadata used by plugin developers. Some developers started using the function to register post type-specific meta keys without that function actually supporting that, as shown in this example:

This does not do what you may think it does:

register_meta( 'book', 'isbn', array( ... ) );

The code above will not register an ISBN number meta key for a “book” post type. It will register that key for a custom object type “book”—which probably doesn’t exist, unless a whole custom meta database table, a custom object database table, and surrounding setup has been implemented.

Those function calls fortunately don’t cause any harm in most cases since WordPress only contains four object types that use metadata (ignoring multisite). It becomes problematic though if the function is used to register metadata for the “post” post type.

This also does not do what you may think it does:

register_meta( 'post', 'some_meta_key', array( ... ) );

Since the “postpost type shares a name with the “postobject type, and since, again, the first parameter of register_meta() is for object type and not for a post type, register_meta() will register that meta key and arguments passed for all posts of any subtype (post type).

If you’re reading this and have used register_meta() in a way where you assumed it could register metadata for a single post type or taxonomy, at present we recommend you switch to using register_rest_field(), which is currently the only (however a little more involved) way to achieve this.

Adding subtype support for metadata behavior

A follow-up ticket #38323 aims to finally implement this missing functionality to register_meta() so that there’s an easy-to-use API to handle subtype-specific metadata in the REST API. It picks up the approach used before, where an additional object_subtype argument in the $args parameter can be used to specify the subtype the meta key should be registered with. Picking up the aforementioned example, you could register the ISBN for a “book” post type via the following code:

register_meta( 'post', 'isbn', array(
	'object_subtype' => 'book',
) );

Open questions regarding subtype handling

Implementing meta registration for subtypes and modifying existing behavior to account for it is rather straightforward, and there is a patch available to experiment with the current approach. There are a couple things that need to be discussed though:

  • How should we deal with or prevent conflicts between meta keys of the same name and object type? The method currently allows to register metadata for an entire object type of course, so we cannot simply change that now and prevent those calls entirely. It appears we have four alternatives here:
    1. Allow meta keys of the same name and object type to be registered in any way. In case a meta key is registered for the current subtype, but also more generally for the object type, the behavior registered for the subtype takes precedence. Meta keys are internally stored nested under their object type and object subtype ($wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ]). This is how the current patch on the ticket works, mainly because it is the most straightforward and flexible approach (it was also how the original code from #35658 worked). However it doesn’t deal with conflicts at all, so it is likely not preferable. An issue would be that on every update of a value, sanitization would happen for both behaviors, easily resulting in invalid data and unexpected errors. Throwing notices would be an option – but it would only help preventing conflicts, not actually prevent them.
    2. Allow meta keys of the same name and object type to be registered either per subtype OR for the entire object type. It would be based on whoever comes first takes priority. Meta keys are internally stored nested under their object type and object subtype ($wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ]). If someone registers a meta key for an object type after someone else already registered the same meta key for a subtype of that object type, the process should be rejected and fail. If someone registers a meta key for an entire object type without the same meta key already being registered for a subtype of that object type, afterwards all meta keys for a subtype of that object type would be rejected. This prevents all possible conflicts since meta keys will only exist once per object type/object subtype combination, but still provides the flexibility of having multiple meta keys of the same name for different object subtypes. The implementation to prevent the conflicts during meta registration would be a little more involved though.
    3. Allow meta keys of the same name and object type to be registered only once in total. It would be based on whoever comes first takes priority. Meta keys are internally stored nested under their object type only, without an additional level for their object subtype ($wp_meta_keys[ $object_type ][ $meta_key ]). Whoever comes first, can register their meta key, every following request to add the same meta key for the same object type afterwards will be rejected. This prevents all possible conflicts since the data structure alone makes it impossible for two meta keys of the same name and object type to be registered. The approach generally appears to not be flexible enough: For example if two different post types were using a meta key of the same name, they wouldn’t be able to co-exist. However, based on the assumption that developers prefix all their metadata (which is probably not taught often enough), this option could very well work and be a straightforward solution.
    4. Allow meta keys of the same name and object type to be registered in any way, but use the behavior registered for the entire object type as a fallback only. In other words, if the same meta key exists with a subtype, only run that logic – for all subtypes where there is no specific behavior available, fall back to the behavior registered on the entire object type. To be precise, the difference between this approach and the first one is that in the first one the behavior (like sanitization and auth) would be executed for both, while here it only happens for the object subtype or the object type when no subtype-specific handling is registered. This is a conflict-free approach that at the same time doesn’t require any restrictions where calls to register_meta() would need to be rejected. The idea of using meta keys registered for an entire object type as fallback is slightly different from the other variants, but it seems to work well. It should still be highlighted that meta keys should be prefixed, but if that is a given, this implementation would even allow an individual plugin to provide more flexible logic for its metadata that may be needed for all post types (general behavior as fallback, subtype-specific behavior where needed).
  • Should we introduce easy-to-use wrapper functions like register_post_meta( $post_type, $meta_key, $args ) and register_term_meta( $taxonomy, $meta_key, $args ) which wrap register_meta() appropriately? The reason those might be useful is that the concept of object types and object subtypes is not commonly exposed in core’s documentation & high-level APIs (think about *_post_meta(), *_term_meta(), *_comment_meta() and *_user_meta() all wrapping the respective lower-level *_metadata() function). By adding these methods we would then recommend to only use those, and never recommend the use of register_meta() itself (as registering metadata for an entire object type is probably not desired). The only downside would be that comments and users don’t really have subtypes, so those two functions would either have a redundant first parameter or not have that parameter at all (which wouldn’t be future-proof).
  • A more specific issue: Should a comment’s comment_type value be considered a subtype? While one could argue that they are subtypes, they are treated entirely differently and do not really define how that comment works or how it can be edited. There’s also no flexible API around it to register additional comment types, and in the REST API there are no individual controllers per comment type. Currently, the patch ignores comment types, but this is still open for discussion.

Upcoming Meeting

The above items will be discussed in next week’s upcoming REST API meeting which will take place at the regular meeting time, Thursday, May 3rd, 2018 at 17:00 UTC in the #core-restapi channel. If you’re interested, please attend this meeting so that we can move forward with this ticket, as it should be included in WordPress 5.0. Also feel free to review the latest patch on #38323 and leave a comment on the ticket. While it is unlikely that this is the final implementation, a lot of it would not need to be adjusted regardless of the outcome for the above questions.

Extra credit goes to @kadamwhite for proofreading and several improvements.

#agenda, #rest-api, #team-update