In version two of the WordPress REST API, several improvements have been made regarding how custom fields are handled in WordPress. If you haven’t downloaded version two yet, you can do so via WordPress.org or by cloning the “develop” branch of the official GitHub repo for the project.
In this article, you’ll learn how to edit and create post meta fields using the WordPress REST API and how to retrieve or update metadata for a post. You’ll also learn how to customize the default endpoints for a post to expose post meta or other data related to a post. While this article mainly discusses post meta, it can be largely applied to user meta as well.
Getting Meta Fields With The REST API
Assuming you have the REST API installed and that you’re familiar with getting and updating posts via the REST API, you probably know how to work with the main post data. You most like know that if you query for a post, your meta fields for the posts are not included.
There is an endpoint for post meta, but it requires authentication. Before we get into this, you need to learn how to expose meta fields in the main post route. This will allow a meta field to show up in a request for a post or a group of posts without any authentication or without using the meta endpoints that require authentication.
Imagine you have a post type called “jedi” and a meta field called “lightsaber_color” and you want all requests to “wp-json/wp/v2/jedi” to show the value of the “lightsaber_color” field. In this situation, you can use the function register_api_field() to add this field to the response.
This function has three arguments: The first is what object type—which in this case means post type—to add the field for. The second is the name of this field—in this case, the name of the meta field. The third is an array of arguments. In the array you need to define the callback function for the names of the functions for getting and updating the value of the field.
The following example, and in a few examples after, will be receiving and updating post meta. To get started, you’ll set up some generic callback functions:
function slug_get_post_meta_cb( $object, $field_name, $request ) { return get_post_meta( $object[ 'id' ], $field_name ); } function slug_update_post_meta_cb( $value, $object, $field_name ) { return update_post_meta( $object[ 'id' ], $field_name, $value ); }
Keep in mind that these two functions will only work if the name of the field registered on the API is the same as the meta key. Also, be sure that the meta key is not protected. If the names don’t match, a custom callback will be needed. I will address protected meta—keys beginning with an underscore—shortly.
These two functions map arguments properly from the REST API to the familiar get_post_meta and update_update_meta functions. It’s important to note that a “field” doesn’t need to be treated as a meta field. You can declare a field of any name and provide a callback function to do anything you need with register_rest_field().
With these two functions you can register your API field:
add_action( 'rest_api_init', function() { register_api_field( 'jedi', 'lightsaber_color', array( 'get_callback' => 'slug_get_post_meta_cb', 'update_callback' => 'slug_update_post_meta_cb', 'schema' => null, ) ); });
Now, whenever a request is made to “wp-json/wp/v2/jedi/,” a field “lightsaber_color” with the value of the corresponding meta field for the post(s) in the post type Jedi will be added to the data.
Creating And Editing Meta Fields With The REST API
If you make an authenticated request to the API, all posts will have additional endpoints available for meta.
For example, “wp-json/wp/v2/posts/1/meta” will show all non-protected meta fields for post ID 1, but only for authenticated requests.
If you want to make any meta_data or other related data publicly available, then register_api_field() is the only way to expose this data without a custom endpoint to non-authenticated users.
The meta endpoint for a post will show the meta IDs of each meta key. This is useful for updating an existing meta field.
For example, if you want to add a value for lightsaber color to a post in the jedi post type of ID 42, you would would make a POST request to “wp-json/wp/v2/jedi/42/meta” and, in the body of that request, include the meta key you were updating and the new value.
In order to update that key, you need to find the meta ID by making a request to “wp-json/wp/v2/jedi/42/meta,” then use that ID in a new POST request to that meta IDs endpoint—for example, if the meta ID was 100, it would be “wp-json/wp/v2/jedi/42/meta/100.”
Here’s an example that uses the WordPress HTTP API to create the meta field:
//get URL for post 1's meta $url = rest_url( 'wp/v2/posts/1/meta' ); //add basic auth headers (don't use in production) $headers = array ( 'Authorization' => 'Basic ' . base64_encode( 'admin' . ':' . 'password' ), ); //prepare body of request with meta key/ value $body = array( 'key' => 'lightsaber_color', 'value' => 'blue' ); //make POST request $response = wp_remote_request( $url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //if response is not an error, echo color and get meta ID of new meta key $body = wp_remote_retrieve_body( $response ); if ( ! is_wp_error( $body ) ) { $body = json_decode( $body ); $meta_id = $body->id; echo "Color is " . $body->value; if ( $meta_id ) { //add meta ID to end of URL $url .= '/' . $meta_id; //this time just need to send value $body = array( 'value' => 'green' ); $response = wp_remote_request( $url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //if not an error echo new color $body = wp_remote_retrieve_body( $response ); if ( ! is_wp_error( $body ) ) { $body = json_decode( $body ); echo "Color is now " . $body->value; } } }
You’re probably not going to be editing a meta field immediately after creating it. To illustrate how you could make changes, however, I’ll demonstrate how to edit the SEO title and SEO description fields added by Yoast’s WordPress SEO plugin, while also making them available in the response for posts.
This will help bring the discussion full-circle.
The meta fields “yoast_wpseo_title” and “yoast_wpseo_metadesc” are considered protected by WordPress. To make an API request for them, you must first remove its protection. For this you can use the filter “is_protected_meta.”
Since this filter isn’t part of the REST API, it affects any use of these fields. Make sure the protection is only removed during API calls—which can be determined based on the constant REST_REQUEST.
First, hook the filter “is_protected_meta.” If the REST_REQUEST constant is true, then, in the callback, set these two fields as unprotected.
Here is what it looks like:
add_filter( 'is_protected_meta', function( $protected, $meta_key ) { if ( '_yoast_wpseo_title' == $meta_key || '_yoast_wpseo_metadesc' == $meta_key && defined( 'REST_REQUEST' ) && REST_REQUEST ) { $protected = false; } return $protected; }, 10, 2 );
Now, you can register both fields in the API for posts:
register_api_field( 'post', '_yoast_wpseo_metadesc', array( 'get_callback' => 'slug_get_post_meta_cb', 'update_callback' => 'slug_update_post_meta_cb', 'schema' => null, ) );
With all of that in place, you can now search for their IDs with a GET request to the meta endpoint for a post. This will allow you to create a URL to send a POST request to update the values. Of course, if there is no value, you will need to create one.
Here is a function that relies on the fields being registered and unprotected, similar to the example code above, that can update or create values in the two fields for a given post ID:
function slug_update_wp_seo_via_api( $post_id, $meta_title, $meta_desc, $auth_headers ) { //get URL for post's meta $meta_url = rest_url( 'wp/v2/posts/' . $post_id . '/meta' ); //add basic auth headers (don't use in production) $headers[ 'Authorization' ] = $auth_headers; //make GET request for all meta $response = wp_remote_request( $meta_url, array( 'method' => 'GET', 'headers' => $headers, ) ); //if response is not an error continue $body = wp_remote_retrieve_body( $response ); if ( ! is_wp_error( $body ) ) { //get meta data into an array $meta_data = json_decode( $body ); //set variables for IDs of both fields to false $meta_title_id = $meta_desc_id = false; //loop until we found each foreach( $meta_data as $meta ) { //if current meta key is title set variable for it if ( '_yoast_wpseo_title' == $meta->key ) { $meta_title_id = $meta->id; } //if current meta key is description set variable for it if ( '_yoast_wpseo_metadesc' == $meta->key ){ $meta_desc_id = $meta->id; } //break loop if we have both if ( false != $meta_desc_id && false != $meta_title_id ) { break; } } //see if we have a value for meta title ID //if so update, if not create if ( $meta_title_id ) { //add meta ID to end of URL $url = $meta_url . '/' . $meta_title_id; //put value in body $body = array( 'value' => $meta_title ); //update via POST request $response = wp_remote_request( $url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //fire an action to expose response when updating SEO title do_action( 'slug_wp_seo_title_updated_via_api', $response, $post_id ); }else { //put value and key in body $body = array( 'key' => '_yoast_wpseo_title', 'value' => $meta_title ); //Create via POST request $response = wp_remote_request( $meta_url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //fire an action to expose response when creating SEO title do_action( 'slug_wp_seo_title_created_via_api', $response, $post_id ); } //see if we have a value for meta description ID //if so update, if not create if ( $meta_desc_id ) { //add meta ID to end of URL $url = $meta_url . '/' . $meta_desc_id; //put value in body $body = array( 'value' => $meta_desc ); //update via POST request $response = wp_remote_request( $url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //fire an action to expose response when updating SEO description do_action( 'slug_wp_seo_desc_updated_via_api', $response, $post_id ); }else { //put value and key in body $body = array( 'key' => '_yoast_wpseo_metadesc', 'value' => $meta_desc ); //Create via POST request $response = wp_remote_request( $meta_url, array( 'method' => 'POST', 'headers' => $headers, 'body' => $body ) ); //fire an action to expose response when creating SEO description do_action( 'slug_wp_seo_desc_created_via_api', $response, $post_id ); } } }
You Learned A Lot More Than How To Work With Post Meta
You should now successfully be able start working with post meta for post types using the WordPress REST API. Just keep in mind that register_api_field can be used for a lot more than meta fields: It is a gateway to viewing and updating any type of data you want.
What you’ve learned in this article should equip you to use register_api_field() with any function you need to show or save data. This handy little function is one of the most useful parts of the API. Use its power wisely.
Share your thoughts and questions in the comments below!
14 Comments