How can I register a new REST context?

GraphQL is more flexible in general than the built-in REST API, regarding customizing the response. GraphQL is currently not shipped with the WordPress core, but is available as a plugin.

Let’s look at the existing endpoints for the WP_Post object(s).

We note that it is possible to use the _fields query parameter to include specific fields in the general REST response.

Here are few ways we can define new custom schema contexts, to the existing endpoints, using the schema setup in:

  • register_rest_field() when adding new rest fields.
  • register_post_meta() when adding existing post meta as new rest fields.
  • WP_REST_Posts_Controller::get_item_schema() for the existing rest fields and append new contexts to their item schema via the rest_{$this->post_type}_item_schema filter. This filter was added in 47779.

Let’s look at two examples:

Example: New custom context for a new rest field

Here’s an example for adding the new foo rest field with the bar value for the post object type and the custom wpse_react context:

add_action( 'rest_api_init', function () {
    register_rest_field( 'post', 'foo',
        array(
            'get_callback' => function ( $object ) {
                return 'bar'; 
            },
            'schema'       => array(
                'type'     => 'string',
                'context'  => array( 'wpse_react' ),
                 // ... etc
            ),
            // ... etc
        )
    );
} );

Then try e.g. the GET request /wp-json/wp/v2/posts/?context=wpse_react and verify that the new field is displayed under the wpse_react context.

Example : New custom context for existing rest fields

We can filter the existing post type schema with rest_{$this->post_type}_item_schema. So for the post post type we can add the custom wpse_react context to e.g. the existing rest fields date, title.rendered and content.rendered:

add_filter( 'rest_post_item_schema', function( $schema ) {
    // 1st level.
    foreach ( ['date','title','content'] as $item ) {
        if ( is_array( $schema['properties'][$item]['context'] ) ) {
            $schema['properties'][$item]['context'][] = 'wpse_react';
        }
    }
    // 2nd level for the 'rendered' part.
    foreach ( ['content', 'title'] as $item ) {
        if ( is_array( $schema['properties'][$item]['properties']['rendered']['context'] ) ) {
            $schema['properties'][$item]['properties']['rendered']['context'][] = 'wpse_react';
        }
    }
    return $schema;
} );    

Then try e.g. the GET request /wp-json/wp/v2/posts/?context=wpse_react.

Extending existing REST controllers

If we need to modify existing endpoints into our own custom endpoints, it is informative to look at e.g. how the Blocks controller extends the Posts controller to remove fields and change the contexts:

class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller

https://github.com/WordPress/wordpress-develop/blob/5.9/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php

where it overrides these methods with:

/**
 * Filters a response based on the context defined in the schema.
 *
 * @since 5.0.0
 *
 * @param array  $data    Response data to fiter.
 * @param string $context Context defined in the schema.
 * @return array Filtered response.
 */
public function filter_response_by_context( $data, $context ) {
    $data = parent::filter_response_by_context( $data, $context );

    /*
     * Remove `title.rendered` and `content.rendered` from the response. It
     * doesn't make sense for a reusable block to have rendered content on its
     * own, since rendering a block requires it to be inside a post or a page.
     */
    unset( $data['title']['rendered'] );
    unset( $data['content']['rendered'] );

    return $data;
}

and

/**
 * Retrieves the block's schema, conforming to JSON Schema.
 *
 * @since 5.0.0
 *
 * @return array Item schema data.
 */
public function get_item_schema() {
    // Do not cache this schema because all properties are derived from parent controller.
    $schema = parent::get_item_schema();

    /*
     * Allow all contexts to access `title.raw` and `content.raw`. Clients always
     * need the raw markup of a reusable block to do anything useful, e.g. parse
     * it or display it in an editor.
     */
    $schema['properties']['title']['properties']['raw']['context']   = array( 'view', 'edit' );
    $schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' );

    /*
     * Remove `title.rendered` and `content.rendered` from the schema. It doesn’t
     * make sense for a reusable block to have rendered content on its own, since
     * rendering a block requires it to be inside a post or a page.
     */
    unset( $schema['properties']['title']['properties']['rendered'] );
    unset( $schema['properties']['content']['properties']['rendered'] );

    return $schema;
}