Miscellaneous REST API improvements in WordPress 6.1

Search REST resources by ID across subtypes

WordPress 6.1 includes an enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature. to the search controller, #56546, which makes it possible to retrieve a term or post object over 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”) https://developer.wordpress.org/rest-api/. without knowing anything but that resource’s ID and object type.

get_post can retrieve a post of any post type so long as you know the post’s numeric ID, and get_term can retrieve a term from any taxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies.. Because REST objects are segregated by post type-specific endpoints, however, there has not been a clear way to get a Post with ID 78 if you don’t know whether it is a page, post, or my-cpt.

The coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. /search endpoint now supports ?include and ?exclude parameters which take a list of IDs, and limit results to posts matching those IDs.

Examples:

  • To get post 78 when you don’t know its post type,
    • /wp/v2/search?include=78
  • To get posts 78 and 79 only if they are in the page post type,
    • /wp/v2/search?include=78,79&subtype=page
  • To search posts excluding post 78,
    • /wp/v2/search?exclude=78
  • To get term 87,
    • /wp/v2/search?type=term&include=78
  • To get term 87 only if it is a categoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging.,
    • /wp/v2/search?type=term&include=78&subtype=category
  • To search terms excluding terms 87 and 88,
    • /wp/v2/search?exclude=87,88

The search endpoint supports the _embed 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. parameter, so developers can therefore use the search endpoint to retrieve a full post or term response object in one request knowing only those object’s IDs.

As an example of how this could be used, imagine a custom blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. which relates to a specific post. As of WordPress 6.1 developers can implement that block knowing only the related post’s ID, and could then create a hook to search for that post by ID and retrieve it using the Block Editor’s existing entity system:

/**
 * Dictionary of requested items: keep an in-memory list of the type (if known)
 * for each requested ID, to limit unnecessary API requests.
 */
const typeById = {};
​
/**
 * Query for a post entity resource without knowing its post type.
 *
 * @param {number} id Numeric ID of a post resource of unknown subtype.
 * @returns {object|undefined} The requested post object, if found and loaded.
 */
function usePostById( id ) {
    const type = typeById[ id ];
​
    useEffect( function() {
        if ( ! id || typeById[ id ] ) {
            return;
        }
​
        apiFetch( {
            path: `/wp/v2/search?type=post&include=${ id }&_fields=id,subtype`,
        } ).then( ( result ) => {
            if ( result.length ) {
                typeById[ id ] = result[0].subtype;
            }
        } );
    }, [ id ] );
​
    return useSelect( function( select ) {
        if ( ! id || ! type ) {
            return undefined;
        }
        return select( 'core' ).getEntityRecord( 'postType', type, id );
    }, [ id, type ] );
}

Pretty-printing REST endpoint JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. responses

WordPress 6.1 also introduces support for returning pre-formatted JSON from the REST API. #41998 lets developers request formatted JSON using a new _pretty query parameter or a filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output., particularly useful when querying via curl or other tools which do not provide an option to format responses.

To format the JSON returned from a specific endpoint request, append the ?_pretty query parameter to the endpoint URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org.

To instruct WordPress to pretty-print all REST response bodies, a developer can use the rest_json_encode_options filter:

function myproject_pretty_print_rest_responses( $options ) {
        $options |= JSON_PRETTY_PRINT;
​
        return $options;
}
add_filter( 'rest_json_encode_options', 'myproject_pretty_print_rest_responses', 10 );

A developer or site owner may also disable pretty-printing globally using the same filter:

function myproject_disable_rest_pretty_printing( $options ) {
        $options &= ~JSON_PRETTY_PRINT;
​
        return $options;
}
add_filter( 'rest_json_encode_options', 'myproject_disable_rest_pretty_printing', 10 );

Filters are applied after, and can override, the _pretty query parameter.

Thanks to @spacedmonkey for peer review.

#core-restapi, #dev-notes, #dev-notes-6-1, #rest-api

Performance improvements to the REST API

WordPress 6.1 brings a number of key improvements to 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”) https://developer.wordpress.org/rest-api/. to increase performance. These improvements decrease the number of database queries that are run on each REST API request. 

Avoid unnecessarily preparing item links

Prior to WordPress 6.1, the prepare_links method in the REST API was called in all controllers. If the _fields parameter is passed to the REST API request, it might mean that the links field is not requested and would never be returned in the response. This is wasteful, as prepare_links can contain database calls or other complex logic that would be run even if it never returned the response. 

In 6.1 prepare_links are only called if requested in the response, when links are requested in fields or the _embedded parameter is passed. As part of this work, the taxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies. and post type controllers have now been updated to implement said prepare_links method to bring them in line with other REST API controllers. 

This is the code example of implementing this change in custom REST API controllers.

Before

$response = rest_ensure_response( $data );
$links = $this->prepare_links( $post );
$response->add_links( $links );

return $response;

After

$response = rest_ensure_response( $data );

if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
  $links = $this->prepare_links( $post );
  $response->add_links( $links );
}

return $response;

This logic conditionally calls prepare_links only if _links or _embedded is requested. This logic only applies when using the _fields query parameter. When not using _fields, links are included in the response as normal. 

For more info see TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. ticketticket Created for both bug reports and feature development on the bug tracker.: #52992, #56019, #56020

Improvement to the Posts controller

When running profiling tools against the responses of REST API requests, it was discovered that post controllers request a lot of linked data to each post. For example, when returning a post in a REST API response, linked data such as author (user), featured imageFeatured image A featured image is the main image used on your blog archive page and is pulled when the post or page is shared on social media. The image can be used to display in widget areas on your site or in a summary list of posts., and parent post were all requested. As these linked items were not primed in caches, it could mean that for each post in the REST API response there would be 3 separate database queries: one for the user, one for the featured image, and another for the parent post. 

In WordPress 6.1 all the caches are primed in a single database query and there are new helper functions to enable this:

update_post_author_caches

Takes an array of posts and primes users caches in a single query. 

update_post_parent_caches

Takes an array of posts and primes post parents in a single query. 

update_menu_item_cache

Takes an array of posts and primes post / terms link to menu items single query. 

The existing function update_post_thumbnail_cache was used to prime featured image caches. These functions are also being rolled out to other parts of the coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. that can benefit from priming caches in a single place. 

For more info see Trac tickets: #55592, #55593, #55620    

Improvements to other controllers

The comments and user controllers have also been improved: User controller now primes user 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. in a single query and the comments controller now primes the linked post cache in a single query. Improvements were made to the post search controller to improve database performance along with the media controller. 

For more info see Trac tickets: #55674, #56272, #55677, #55716

Props to @flixos90 and @milana_cap for peer review and @mxbclang and @webcommsat for proofreading.

#6-1, #core-restapi, #dev-notes, #dev-notes-6-1, #performance, #rest-api

Menus endpoints in WordPress 5.9

WordPress 5.9 adds three new 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”) https://developer.wordpress.org/rest-api/. endpoints to manage menus and menu locations. These menus endpoints are used the in new navigation block.

Before discussing menu endpoints, it’s worth noting how menus are currently stored. Navigation menus are stored using the nav_menu taxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies. and the nav_menu_item post type. A menu is stored as a term and acts like a container for a number of menu items. Menu items are stored as posts. Menus and menu items also have custom fields stored in 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. and in various options.

Accessible via /wp/v2/menus, the menus endpoint allows for performing CRUDCRUD Create, read, update and delete, the four basic functions of storing data. (More on Wikipedia.) operations on menu data. This endpoint extends the WP_REST_Terms_Controller class, mapping fields to the menus and adding functionality like auto_add, that automatically adds new pages to menus when created. A GET request includes the list of menus, but does not contain the list of menu items. To get this data, the menu items endpoint can be used.

Example output from request:

{
    "auto_add": false
    "description": "",
    "id": 36,
    "locations": ['wporg_header_subsite_nav', 'primary'],
    "meta": [],
    "name": "Navigation",
    "slug": "navigation",
}

Accessible via /wp/v2/menu-items, the menu items endpoint allows for performing CRUD operations on menu items and assigning them to menus. This endpoint extends the WP_REST_Posts_Controller class, mapping fields and adding custom functionality. Menu items can only be assigned to one menu at a time unlike other taxonomies.

Many menu items have an associated object that the menu item links to. For instance, a link to a page will have the object set to page and the object_id set to the WordPress Post ID of that page. When using this endpoint, it may be useful to get information about that linked object. For example the page’s title. This information is not included in the response by default, but the REST API has a feature to embed this information by using the _embed query parameter. For example, making a GET request to /wp/v2/menu-item/8874?_embed=true will result in the following response:

{
     "attr_title": "",
     "classes": [''],
     "description": "",
     "id": 8874,
     "invalid": false,
     "menu_order": 1,
     "menus": 363,
     "meta": {},
     "object": "page",
     "object_id": 8823,
     "parent": 0,
     "status": "publish",
     "target": "",
     "title": {rendered: 'Tickets'},
     "type": "post_type",
     "type_label": "Page",
     "url": "https://make.wordpress.org/core/reports/",
     "xfn": [""],
     "_embedded": {
              wp:menu-item-object: [
                   {
                      "author": 5286302,
                      "date": "2014-01-29T19:49:26",
                      "featured_media": 0,
                      "id": 8823,
                      "link": "https://make.wordpress.org/core/reports/",
                      "slug": "reports",
                      "title": { "rendered": "Bug Reports"},
                      "type": "page",
                   }
              ],
              wp:term: [
                    {
                       "id": 363,
                       "name": "Navigation",
                       "slug": "navigation"
                    }
              ]
    },
}

Accessible via /wp/v2/menu-locations, the menu locations endpoint returns a list of menu locations registered with the register_nav_menus function. To assign a menu to a particular location, use the menus endpoint, by passing an array of menu location keys.

Example output from request:

{
     "description": "Desktop Horizontal Menu"
     "menu": 90
     "name": "primary"
}

Batching requests

Both the menu and menu item endpoints, both support the batching of requests, introduced in WordPress 5.6. This means that more than one menu / menu item can be updates / created in a simple request to the 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..

Access Control

To access data from any of the menus endpoints, requests must be made by a logged in user with the edit_theme_options capability. By default, only users with the Administrator role have this capability. This means that menu data is not publically exposed via the REST API. Ticketticket Created for both bug reports and feature development on the bug tracker. #54304 provides a means for developers to opt-in to exposing this data publicly. The REST API team hopes to implement this feature in a near future release of WordPress.

These endpoints were first developed as a feature pluginFeature Plugin A plugin that was created with the intention of eventually being proposed for inclusion in WordPress Core. See Features as Plugins. on GitHub. For those using this 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 WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party, after upgrading to WordPress 5.9, the plugin can be deactivated and removed as all of it’s functionality is now included in WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress..

Props to @timothyblynjacobs for peer review.

#5-9, #dev-notes, #rest-api

REST API Changes in WordPress 5.8

The following is a snapshot of some of the changes to 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”) https://developer.wordpress.org/rest-api/. in WordPress 5.8. For more details, see the full list of closed tickets.

Widgets

WordPress 5.8 sees the introduction of a new blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience.-based widgets editor and with it the creation of several REST API endpoints dedicated to widgetWidget A WordPress Widget is a small block that performs a specific function. You can add these widgets in sidebars also known as widget-ready areas on your web page. WordPress widgets were originally created to provide a simple and easy-to-use way of giving design and structure control of the WordPress theme to the user. management. Before diving in to how the new endpoints operate, I’d like to provide some background about how widgets work that should make the following sections more clear.

Instance Widgets

The predominant way to create widgets is to subclass the WP_Widget base class and register the widget with register_widget. These are referred to as “multi” widgets. These widgets have multiple instances that are identified by their number, an incrementing integer for each widget type.

Each instance has its own setting values. These are stored and fetched by WP_Widget which allows for the REST API to include these values. However, since a widget’s instance can contain arbitrary data, for example a DateTime object, the REST API cannot always serialize a widget to JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML.. As such, a widget’s data is always serialized using the PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 or higher serialize function and then base64 encoded. This data is also exposed with a hash value which is a wp_hash signature of this value to prevent clients from sending arbitrary data to be deserialized with unserialize.

For widgets that can be safely accept and expose their instance data as JSON, pass the show_instance_in_rest flag in the $widget_options parameter.

class ExampleWidget extends WP_Widget {
    ...
    /**
     * Sets up the widget
     */
    public function __construct() {
        $widget_ops = array(
            // ...other options here
            'show_instance_in_rest' => true,
            // ...other options here
        );
        parent::__construct( 'example_widget', 'ExampleWidget', $widget_ops );
    }
    ...
}

Reference Widgets

Far less common, but still supported, are widgets that are registered using wp_register_sidebar_widget and wp_register_widget_control directly. These are referred to as “reference”, “function-based”, or “non-multi” widgets. These widgets can store their data in an arbitrary location. As such, their instance values are never included in the REST API.

Widget Types

Accessible via /wp/v2/widget-types, the widget types endpoint describes the different widget types that are registered on the server. The endpoint is accessible to users who have permission to edit_theme_options. By default, this is limited to Administrator users.

Response Format

{
  "id": "pages",
  "name": "Pages",
  "description": "A list of your site’s Pages.",
  "is_multi": true,
  "classname": "widget_pages",
  "_links": {
    "collection": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/widget-types"
      }
    ],
    "self": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/widget-types/pages"
      }
    ]
  }
}

Encode Endpoint

Multi widgets have access to the /wp/v2/widget-types/<widget>/encode endpoint. This endpoint is used to convert htmlHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. form data for the widget to the next instance for the widget, render the widget form, and render the widget preview.

For example, let’s say we want to interact with 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. widget. First, we’ll want to request the widget form from the server.

POST /wp/v2/widget-types/meta/encode
{
  "instance": {},
  "number": 1
}

For now, let’s assume we’re working with a new widget. The instance is empty because this is a new widget, so we’ll be rendering an empty form. The number argument can be omitted, but including one is recommended to provide stable input ids. You’ll receive a response similar to this. The widget preview has been snipped for brevity.

{
  "form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"\" /></p>",
  "preview": "<div class=\"widget widget_meta\">...</div>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjA6IiI7fQ==",
    "hash": "77e9f20acb54fa4f77de5a865333c0e6",
    "raw": {
      "title": ""
    }
  }
}

The provided form can then be rendered and edited by the user. When you want to render a new preview or are ready to begin saving, call the encode endpoint again with the url encoded form data and the instance value returned from the first response.

POST /wp/v2/widget-types/meta/encode
{
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjA6IiI7fQ==",
    "hash": "77e9f20acb54fa4f77de5a865333c0e6",
    "raw": {
      "title": ""
    }
  },
  "number": 1,
  "form_data": "widget-meta%5B1%5D%5Btitle%5D=Site+Info"
}

The REST API will call the widget’s update function to calculate the new instance based on the provided form data. The instance object can then be used to save a widget via the widgets endpoint.

{
  "form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"Site Info\" /></p>",
  "preview": "<div class=\"widget widget_meta\">...</div>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgSW5mbyI7fQ==",
    "hash": "0e9a5bff2d28cba322c8cd27cd4e77af",
    "raw": {
      "title": "Site Info"
    }
  }
}

Widgets Endpoint

The widgets endpoint is used for performing CRUDCRUD Create, read, update and delete, the four basic functions of storing data. (More on Wikipedia.) operations on the saved widgets. Like the widget types endpoint, the widgets endpoints required the edit_theme_options capability to access.

To retrieve widgets, make a GET request to the /wp/v2/widgets endpoint. The sidebar parameter can be used to limit the response to widgets belonging to the requested sidebarSidebar A sidebar in WordPress is referred to a widget-ready area used by WordPress themes to display information that is not a part of the main content. It is not always a vertical column on the side. It can be a horizontal rectangle below or above the content area, footer, header, or any where in the theme..

To create a widget, for instance the widget from our previous example, make a POST request to the /wp/v2/widgets endpoint. The instance is the same value returned from the encode endpoint. The id_base is the unique identifier for the widget type and sidebar is the id of the sidebar to assign the widget to. Both are required.

POST /wp/v2/widgets
{
	"instance": {
		"encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgSW5mbyI7fQ==",
		"hash": "0e9a5bff2d28cba322c8cd27cd4e77af",
		"raw": {
			"title": "Site Info"
		}
	},
	"sidebar": "sidebar-1",
	"id_base": "meta"
}

The endpoint will return information about the newly created widget.

{
  "id": "meta-1",
  "id_base": "meta",
  "sidebar": "sidebar-1",
  "rendered": "<div class=\"widget widget_meta\">...</div>",
  "rendered_form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"Site Info\" /></p>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgTWV0YSI7fQ==",
    "hash": "dac44c3ebfa0428fed61829fa151e4e8",
    "raw": {
      "title": "Site Info"
    }
  },
  "_links": {
    "self": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/widgets/meta-1"
      }
    ],
    "collection": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/widgets"
      }
    ],
    "about": [
      {
        "embeddable": true,
        "href": "https://trunk.test/wp-json/wp/v2/widget-types/meta"
      }
    ],
    "wp:sidebar": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/sidebars/sidebar-1/"
      }
    ],
    "curies": [
      {
        "name": "wp",
        "href": "https://api.w.org/{rel}",
        "templated": true
      }
    ]
  }
}

Since the meta widget (and all other built-in widgets) is registered with show_instance_in_rest enabled you could bypass the encode endpoint and use instance.raw instead. For example, if we wanted to update the widget to have a new title, we could make the following PUT request to /wp/v2/widgets/meta-1.

PUT /wp/v2/widgets/meta-1
{
	"instance": {
		"raw": {
			"title": "Site Meta"
		}
	}
}

A PUT request can also be made to update the sidebar assigned to a widget by passing a new sidebar id in the request.

To delete a widget, send a DELETE request to the individual widget route. By default, deleting a widget will move a widget to the Inactive Widgets area. To permanently delete a widget, use the force parameter. For example: DELETE /wp/v2/widgets/meta-1?force=true.

Sidebars Endpoints

Found under /wp/v2/sidebars, the sidebars endpoint is used to manage a site’s registered sidebars (widget areas) and their assigned widgets. For example, the following is the response for the first footer area in the Twenty Twenty theme.

{
  "id": "sidebar-1",
  "name": "Footer #1",
  "description": "Widgets in this area will be displayed in the first column in the footer.",
  "class": "",
  "before_widget": "<div class=\"widget %2$s\"><div class=\"widget-content\">",
  "after_widget": "</div></div>",
  "before_title": "<h2 class=\"widget-title subheading heading-size-3\">",
  "after_title": "</h2>",
  "status": "active",
  "widgets": [
    "recent-posts-2",
    "recent-comments-2",
    "meta-1"
  ],
  "_links": {
    "collection": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/sidebars"
      }
    ],
    "self": [
      {
        "href": "https://trunk.test/wp-json/wp/v2/sidebars/sidebar-1"
      }
    ],
    "wp:widget": [
      {
        "embeddable": true,
        "href": "https://trunk.test/wp-json/wp/v2/widgets?sidebar=sidebar-1"
      }
    ],
    "curies": [
      {
        "name": "wp",
        "href": "https://api.w.org/{rel}",
        "templated": true
      }
    ]
  }
}

The widgets property contains an ordered list of widget ids. While all other properties are readonly, the widgets property can be used to reorder a sidebar’s assigned widgets. Any widget ids omitted when updating the sidebar will be assigned to the Inactive Widgets sidebar area.

For example, making a PUT request to /wp/v2/sidebars/sidebar-1 with the following body will remove the Recent Comments widget, and move the Meta widget to the top of the sidebar.

PUT /wp/v2/sidebars/sidebar-1
{
  "widgets": [
    "meta-1",
    "recent-posts-2"
  ]
}

For more information about the changes to widgets in 5.8, check out the Block-based Widgets Editor dev notedev note Each important change in WordPress Core is documented in a developers note, (usually called dev note). Good dev notes generally include a description of the change, the decision that led to this change, and a description of how developers are supposed to work with that change. Dev notes are published on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev notes is particularly important when plugin/theme authors and WordPress developers need to be aware of those changes.In general, all dev notes are compiled into a Field Guide at the beginning of the release candidate phase..

Additional Changes

Posts Collection Tax Query Accepts operator

By default, a post must contain at least one of the requested terms to be included in the response. As of [51026], the REST API accepts a new operator property that can be set to AND to require a post to contain all of the requested terms.

For example, /wp/v2/posts?tags[terms]=1,2,3&tags[operator]=AND will return posts that have tags with the ids of 1, 2, and 3.

See #41287 for more details.

Props to @noisysocks for reviewing.

#5-8, #dev-notes, #rest-api

REST API Changes in WordPress 5.7

The following is a snapshot of some of the changes to 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”) https://developer.wordpress.org/rest-api/. in WordPress 5.7. For more details, see the full list of closed tickets.

Endpoint Changes

Posts Collection Tax Query Accepts include_children

Introduced in 50157, the REST API posts collection endpoints have been updated to allow a more complex syntax for specifying the tax_query used when querying posts. Each taxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies.’s query parameters can now both accept a list of term ids or an object with a terms property. 

Hierarchical taxonomies support an include_children property alongside terms. By default it’s disabled, but if set to true the flag is enabled for the generated tax_query to enable searching for posts that have the given terms or terms that are children of the given terms.

import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';

const query = {
	categories: {
		terms: [ 3, 5, 7 ],
		include_children: true,
	},
};
apiFetch( { path: addQueryArgs( '/wp/v2/posts', query ) } );

See #39494 for more details.

Support modified_before and modified_after Query Parameters

Introduced in 50024 the REST API posts collection endpoints now accept modified_before and modified_after query parameters to query posts based on the post modified date instead of the post published date.

import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';

const query = {
	modified_after: '2020-12-01T00:00:00Z',
	modified_before: '2020-12-31T23:59:59Z',
};
apiFetch( { path: addQueryArgs( '/wp/v2/posts', query ) } );

As a result of this change, the posts controller now uses a date_query with a separate clause for each date related query parameter instead of a single clause with the before and after flags set.

See #50617 for more details.

Multiple 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. Values for a Key Can Be Deleted by Sending an Empty Array

Previously, to remove all values for a specific meta key, passing null as the value for that meta key was required. After 49966 an empty array can be passed instead.

register_post_meta( 'post', 'my_meta_key', array( 
	'type' => 'string',
	'show_in_rest' => true,
) );
import apiFetch from '@wordpress/api-fetch';

const data = {
	meta: {
		my_meta_key: [],
	}
};
apiFetch( { data, method: 'PUT', path: '/wp/v2/posts/1' } );

See #50790 for more details.

All Themes are Returned via the Themes Controller

As of 49925, the themes endpoint now returns all themes installed on the site, not just the active theme. By default, the endpoint returns both active and inactive themes, but the status query parameter can be used to limit the list to themes with the desired status.

Note, the theme_supports value is only exposed for the active theme. The field is omitted for inactive themes since declaring the supported theme features requires calls to add_theme_support which can only be done if the theme is active.

When querying solely for active themes, the only permission required is to be able to edit posts of any show_in_rest post type. However when querying for inactive themes the switch_themes or manage_network_themes capability is required.

In addition to the changes to the collection endpoint, a new single theme endpoint is available. For example, /wp/v2/themes/twentytwentyone will return information about Twenty Twenty One. For convenience, a link is also added to the currently active theme in the REST API Index if the current user has the requisite permissions. This is the recommended way for applications to discover the currently active theme.

{
  "name": "WordPress Develop",
  "_links": {
    "help": [
      {
        "href": "http://v2.wp-api.org/"
      }
    ],
    "wp:active-theme": [
      {
        "href": "https://wordpress.test/wp-json/wp/v2/themes/twentytwentyone"
      }
    ],
    "curies": [
      {
        "name": "wp",
        "href": "https://api.w.org/{rel}",
        "templated": true
      }
    ]
  }
}

Lastly, the pageper_pagesearch and context query parameters are no longer exposed in the index as they are not supported by this endpoint.

See #50152 for more details.

Image Editor Accepts a List of Modifiers

The /wp/v2/media/<id>/edit endpoint introduced in WordPress 5.5 came with a limited 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. that accepted top-level rotation and crop declarations. In 50124 this API was made more powerful and flexible by accepting an array of modifications in the new modifiers request parameter.

import apiFetch from '@wordpress/api-fetch';

const data = {
	modifiers: [
		{
			type: 'crop',
			args: {
				left  : 0,
				top   : 0,
				width : 80,
				height: 80
			}
		},
		{
			type: 'rotate',
			args: {
				angle: 90
			}
		}
	]
};
apiFetch( { data, method: 'POST', path: '/wp/v2/media/5/edit' } );

The previous query parameters have been marked as deprecated, but will continue to function as normal and do not currently issue deprecation warnings. Clients are encouraged to switch to the new syntax.

To alleviate server resources, whenever possible, clients should simplify redundant modifications before sending the request.

See #52192 for more details.

Parameter Validation

Non-String Enums

In 50010 support for type coercion was added to the enum JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. Schema keyword. Previously, the enum keyword was validated by perform a strict equality check. For string types this is generally ok, but it prevented using alternative types like number when rich type support isn’t available.

Now the same level of type coercion/sanitization is applied when validating enum as all other validation checks. This means that a value of "1" will be accepted for an enum of [ 0, 1 ]. Additionally, object types now properly ignore key order when checking for equality.

See #51911 for more details.

Validation Error Codes

As of 50007, the rest_validate_value_from_schema function now returns specific error codes for each validation failure instead of the generic rest_invalid_param. For instance, if more array items are given than allowed by maxItems, the rest_too_many_itemserror code will be returned.

See #52317 for more details.

Return Detailed Error Information from Request Validation

Previously when a parameter failed validation only the first error message specified in the WP_Error instance was returned to the user. Since 50150 the REST API will now return detailed error information as part of the details error data key.

{
  "code": "rest_invalid_param",
  "message": "Invalid parameter(s): enum_a, enum_b",
  "data": {
    "status": 400,
    "params": {
      "enum_a": "enum_a is not one of a, b, c.",
      "enum_b": "enum_b is not one of d, e, f."
    },
    "details": {
      "enum_a": {
        "code": "rest_not_in_enum",
        "message": "enum_a is not one of a, b, c.",
        "data": {
          "enum": ["a", "b", "c"]
        }
      },
      "enum_b": {
        "code": "rest_not_in_enum",
        "message": "enum_b is not one of d, e, f.",
        "data": {
          "enum": ["d", "e", "f"]
        }
      }
    }
  }
}

See #50617 for more details.

Application Passwords

Fine Grained Capabilities

When the Application Passwords REST API controllers were introduced, the edit_user meta capability was used for all permission checks. As of 50114, the REST API now uses specific meta capabilities for each action type.

  • create_app_password
  • list_app_passwords
  • read_app_password
  • edit_app_password
  • delete_app_password
  • delete_app_passwords

By default, these capabilities all map to edit_user however they can now be customized by using the map_meta_cap filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output..

See #51703 for more details.

Introspection Endpoint

50065 introduces a new Application Passwords endpoint for introspecting the app password being currently used for authentication. This endpoint is accessible via /wp/v2/users/me/application-passwords/introspect and will return the same information as the other endpoints. This allows for an application to disambiguate between multiple installations of their application which would all share the same app_id.

Clients can use this information to provide UIUI User interface hints about how the user is authenticated, for instance by displaying the App Passwords’s label. Or when their application is uninstalled by the user, they can automatically clean up after themselves by deleting their App Password.

See #52275 for more details.

Unique Application Names

As of 50030 the Application Passwords API enforces that each App Password has a unique name that cannot consist solely of whitespace characters. Additionally, invalidinvalid A resolution on the bug tracker (and generally common in software development, sometimes also notabug) that indicates the ticket is not a bug, is a support request, or is generally invalid. characters are stripped from the provided application name.

See #51941 for more details.

Props to @flixos90, @desrosj for proofreading.

#5-7, #dev-notes, #rest-api

REST API Changes in WordPress 5.6

WordPress 5.6 introduces a number of changes to 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”) https://developer.wordpress.org/rest-api/., some of which have been covered in other dev notesdev note Each important change in WordPress Core is documented in a developers note, (usually called dev note). Good dev notes generally include a description of the change, the decision that led to this change, and a description of how developers are supposed to work with that change. Dev notes are published on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev notes is particularly important when plugin/theme authors and WordPress developers need to be aware of those changes.In general, all dev notes are compiled into a Field Guide at the beginning of the release candidate phase..

Below are some other noteworthy changes that deserve a call out.

Search

The wp/v2/search endpoint introduced in WordPress 5.0 provides a unified interface for searching across multiple content types. WordPress 5.6 adds a terms and post formats search handler. For example, to search across terms in all taxonomies, make the following request: https://example.org/wp-json/wp/v2/search?type=term&search=my-term. To search post-formats, use the post-format type: https://example.org/wp-json/wp/v2/search?type=post-format&search=aside.

Additionally, the REST API search infrastructure no longer requires that the id for each item is an integer. Strings are now an acceptable id type.

See #51458, #51459, #51131.

JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. Schema

multipleOf keyword

The multipleOf keyword allows for asserting that an integer or number type is a multiple of the given number. For example, this schema will only accept even integers.

{
  "type": "integer",
  "multipleOf": 2
}

multipleOf also supports decimals. For example, this schema could be used to accept a percentage with a maximum of 1 decimal point.

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "multipleOf": 0.1
}

See #51022.

minProperties and maxProperties

The minItems and maxItems keywords can be used for the array type. The minProperties and maxProperties introduces this same functionality for the object type. This is helpful when using additionalProperties to have a list of objects with unique keys.

This schema requires an object that specifies at least 1, and at most 3, colors.

{
  "type": "object",
  "additionalProperties": {
    "type": "string",
    "format": "hex-color"
  },
  "minProperties": 1,
  "maxProperties": 3
}
{
  "primary": "#52accc",
  "secondary": "#096484"
}

See #51023.

patternProperties

The patternProperties keyword is similar to the additionalProperties keyword, but allows for asserting that the property matches a regex pattern. The keyword is an object where each property is a regex pattern and its value is the JSON Schema used to validate properties that match that pattern.

For example, this schema requires that each value is a hex color and the property must only contain “word” characters.

{
  "type": "object",
  "patternProperties": {
    "^\\w+$": {
      "type": "string",
      "format": "hex-color"
    }
  },
  "additionalProperties": false
}

When the REST API validates the patternProperties schema, if a property doesn’t match any of the patterns, the property will be allowed and not have any validation applied to its contents. This behaves the same as the properties keyword. If this logic isn’t desired, add additionalProperties to the schema to disallow non-matching properties. See #51024.

oneOf and anyOf

These are advanced keywords that allow for the JSON Schema validator to choose one of many schemas to use when validating a value. The anyOf keyword allows for a value to match at least one of the given schemas. Whereas, the oneOf keyword requires the value match exactly one schema.

For example, this schema allows for submitting an array of “operations” to an endpoint. Each operation can either be a “crop” or a “rotation”.

{
  "type": "array",
  "items": {
    "oneOf": [
      {
        "title": "Crop",
        "type": "object",
        "properties": {
          "operation": {
            "type": "string",
            "enum": [
              "crop"
            ]
          },
          "x": {
            "type": "integer"
          },
          "y": {
            "type": "integer"
          }
        }
      },
      {
        "title": "Rotation",
        "type": "object",
        "properties": {
          "operation": {
            "type": "string",
            "enum": [
              "rotate"
            ]
          },
          "degrees": {
            "type": "integer",
            "minimum": 0,
            "maximum": 360
          }
        }
      }
    ]
  }
}

The REST API will loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop. over each schema specified in the oneOf array and look for a match. If exactly one schema matches, then validation will succeed. If more than one schema matches, validation will fail. If no schemas match, then the validator will try to find the closest matching schema and return an appropriate error message.

operations[0] is not a valid Rotation. Reason: operations[0][degrees] must be between 0 (inclusive) and 360 (inclusive)

See #51025.

Expose all JSON Schema keywords in the index

When making an OPTIONS request to an endpoint, the REST API will return the args that the route accepts. Previously, only a limited subset of JSON Schema keywords were exposed. In WordPress 5.6, now the full list of JSON Schema keywords that the REST API supports will be exposed. See #51020.

More specific validation error codes

When the type of a value is incorrect, rest_validate_value_from_schema now returns rest_invalid_type instead of the generic rest_invalid_param. The validation error code is not currently exposed to REST API clients. This would only effect direct usages of the validation function.

Miscellaneous

The apiRequest library now supports using the PUT and DELETE HTTPHTTP HTTP is an acronym for Hyper Text Transfer Protocol. HTTP is the underlying protocol used by the World Wide Web and this protocol defines how messages are formatted and transmitted, and what actions Web servers and browsers should take in response to various commands. methods with servers that didn’t support those methods. This is done by making the request a POST request and passing the original HTTP method in the X-HTTP-Method-Override headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes.. #43605.

The comments controller now uses the rest_get_route_for_post function introduced in WordPress 5.5 to generate the up response link. This function is filterable to allow for custom controllers to properly define their REST API route. #44152.

The REST API now supports a broader range of JSON media types. Previously, only application/json was supported which prevented using subtypes like application/activity+json. The REST API will now json_decode the body of requests using a JSON subtype Content-Type. Additionally, wp_die() now properly sends the error as JSON when a JSON subtype is specified in the Accept header. #49404.

Props @m_butcher for reviewing.

#5-6, #dev-notes, #rest-api

REST API Batch Framework in WordPress 5.6

WordPress 5.6 introduces a framework for making a series of 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”) https://developer.wordpress.org/rest-api/. calls in one request to the server. At its simplest, this is a helpful performance optimization when a large number of write operations need to be made. It also optionally offers basic concurrency controls.

Registration

In order to be used in a batch request, routes must first declare support for the feature during their registration. For example:

register_rest_route( 'my-ns/v1', 'my-route', array(
	'methods'             => WP_REST_Server::CREATABLE,
	'callback'            => 'my_callback',
	'permission_callback' => 'my_permission_callback',
	'allow_batch'         => array( 'v1' => true ),
) );

If the REST API route was implemented using best practices, declaring support should be sufficient for the route to be writable via the batch endpoint. Specifically, these are the things to look out for:

  1. Routes must use the WP_REST_Request object to get all request data. In other words, it shouldn’t access the $_GET, $_POST or $_SERVER variables to get parameters or headers.
  2. Routes must return data. This could be a WP_REST_Response object, a WP_Error object or any kind of JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. serializable data. This means the route must not directly echo the response and die(). For example by using wp_send_json() or wp_die().
  3. Routes must be re-entrant. Be prepared for the same route to be called multiple times in a batch.

Making a Request

To send a batch, make a POST request to https://yoursite.test/wp-json/batch/v1 with an array of the desired requests. For example, the simplest batch request looks like this.

{
  "requests": [
    {
      "path": "/my-ns/v1/route"
    }
  ]
}

Request Format

Each request is an object that can accept the following properties.

{
  "method": "PUT",
  "path": "/my-ns/v1/route/1?query=param",
  "headers": {
    "My-Header": "my-value",
    "Multi": [ "v1", "v2" ]
  },
  "body": {
    "project": "Gutenberg"
  }
}
  • method is the HTTPHTTP HTTP is an acronym for Hyper Text Transfer Protocol. HTTP is the underlying protocol used by the World Wide Web and this protocol defines how messages are formatted and transmitted, and what actions Web servers and browsers should take in response to various commands. method to use for the request. If omitted, the POST method is used.
  • path is the REST API route to call. Query parameters can be included. This property is required.
  • headers is an object of headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. names to a header values. If the header has multiple values, it can be passed as an array.
  • body is the parameters to pass to the route. It is filled in the POST parameter type.

Discovering Max Requests

By default, the REST API accepts up to 25 requests in a single batch. However, this value is filterable so it can be scaled up or down based on server resources.

function my_prefix_rest_get_max_batch_size() {
	return 50;
}

add_filter( 'rest_get_max_batch_size', 'my_prefix_rest_get_max_batch_size' );

As such, clients are strongly encouraged to make a preflight request to discover the limit. For example, making an OPTIONS request to batch/v1 will return the following response.

{
  "namespace": "",
  "methods": [ "POST" ],
  "endpoints": [
    {
      "methods": [ "POST" ],
      "args": {
        "validation": {
          "type": "string",
          "enum": [ "require-all-validate", "normal" ],
          "default": "normal",
          "required": false
        },
        "requests": {
          "type": "array",
          "maxItems": 25,
          "items": {
            "type": "object",
            "properties": {
              "method": {
                "type": "string",
                "enum": [ "POST", "PUT", "PATCH", "DELETE" ],
                "default": "POST"
              },
              "path": {
                "type": "string",
                "required": true
              },
              "body": {
                "type": "object",
                "properties": [],
                "additionalProperties": true
              },
              "headers": {
                "type": "object",
                "properties": [],
                "additionalProperties": {
                  "type": [ "string", "array" ],
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "required": true
        }
      }
    }
  ],
  "_links": {
    "self": [
      {
        "href": "http://trunk.test/wp-json/batch/v1"
      }
    ]
  }
}

The limit is specified in the endpoints[0].args.requests.maxItems property.

Response Format

The batch endpoint will return a 207 status code and the responses of each request in the same order as they were requested. For example:

{
  "responses": [
    {
      "body": {
        "id": 1,
        "_links": {
          "self": [
            {
              "href": "http://trunk.test/wp-json/my-ns/v1/route/1"
            }
          ]
        }
      },
      "status": 201,
      "headers": {
        "Location": "http://trunk.test/wp-json/my-n1/v1/route/1",
        "Allow": "GET, POST"
      }
    }
  ]
}

Internally, the REST API envelopes each response before including it in the responses array.

Validation Modes

By default, each request is processed in isolation. This means that a batch response can contain some requests that were successful, and some that failed. Sometimes it’s desired to only process a batch if all the requests are valid. For instance, 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. https://wordpress.org/gutenberg/ we don’t want to save some menu items, ideally all would be saved or none would.

To accomplish this, the REST API allows for passing a validation mode of require-all-validate. When this is set, the REST API will first check that each request is valid according to WP_REST_Request::has_valid_params() and WP_REST_Request::sanitize_params(). If any request fails validation, then the entire batch is rejected.

In this example, a batch of two requests is made and the second one has failed validation. Since the order of responses is still the same as the order of requests, null is used to indicate that the request didn’t fail validation.

{
  "failed": "validation",
  "responses": [
    null,
    {
      "body": {
        "code": "error_code",
        "message": "Invalid request data",
        "data": { "status": 400 }
      },
      "status": 400,
      "headers": {}
    }
  ]
}

Note: Using require-all-validate is not a guarantee that all requests will be successful. A route callback may still return an error.

Validate Callback

Those WP_REST_Request methods use the validate_callback and sanitize_callback specified for each parameter when the route is registered. In most cases, this will mean the schema based validation.

Any validation done inside the route, for instance in the prepare_item_for_database method, will not cause the batch to be rejected. If this is a concern, it is recommended to move as much validation as possible into the validate_callback for each individual parameter. This can be built on top of the existing schema based validation, for instance.

'post' => array(
	'type'        => 'integer',
	'minimum'     => 1,
	'required'    => true,
	'arg_options' => array(
		'validate_callback' => function ( $value, $request, $param ) {
			$valid = rest_validate_request_arg( $value, $request, $param );

			if ( is_wp_error( $valid ) ) {
				return $valid;
			}

			if ( ! get_post( $value ) || ! current_user_can( 'read_post', $value ) ) {
				return new WP_Error( 'invalid_post', __( 'That post does not exist.' ) );
			}

			return true;
		}
	)
)

Sometimes when performing validation, the full context of the request is needed. Typically, this validation would have been done in prepare_item_for_database, but WordPress 5.6 introduces an alternative. When registering a route, a top-level validate_callback can now be specified. It will receive the full WP_REST_Request object and can return a WP_Error instance or false. The callback won’t be executed if parameter-level validation did not succeed.

register_rest_route( 'my-ns/v1', 'route', array(
	'callback'            => '__return_empty_array',
	'permission_callback' => '__return_true',
	'validate_callback'   => function( $request ) {
		if ( $request['pass1'] !== $request['pass2'] ) {
			return new WP_Error(
				'passwords_must_match',
				__( 'Passwords must match.' ),
				array( 'status' => 400 )
			);
		}

		return true;
	}
) );

Note: Request validation happens before permission checks take place. Keep this in mind when considering whether to moving logic to a validate_callback.

Limitations

No built-in routes currently allow batching. This will be added in a future release, most likely starting immediately with WordPress 5.7.

GET requests are not supported. Developers are instead encouraged to use linking and embedding or utilize parallel requests for the time being.

Further Reading

See #50244, [49252], [48947], [48945].

Props @kadamwhite, @m_butcher, @jeffmatson for reviewing.

#5-6, #dev-notes, #rest-api

Application Passwords: Integration Guide

WordPress 5.6 will finally see the introduction of a new system for making authenticated requests to various WordPress APIs — Application Passwords.

The existing cookie-based authentication system is not being removed, and any custom authentication solutions provided by plugins should continue to operate normally.

For any sites using the Application Passwords feature plugin, it is recommended to deactivate the 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 WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party after upgrading to WordPress 5.6. However, sites won’t experience any errors if the plugin remains active. The current plan is to use the plugin for future prototyping.

Application Password Format

Application Passwords are 24-characters long, generated by wp_generate_password() without the use of special characters — so they consist exclusively of upper-case, lower-case, and numeric characters. For the cryptographically curious, that comes to over 142 bits of entropy.

When presented to the user for entering into an application, they are displayed chunked for ease of use, like so:

abcd EFGH 1234 ijkl MNOP 6789

Application passwords can be used with or without the spaces — if included, spaces will just be stripped out before the password is hashed and verified.

Data Store

WordPress will be storing a user’s application passwords as an array in user 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., similar to how interactive login sessions (via WP_Session_Tokens) are stored already.

The WP_Application_Passwords class has all the methods for storing and retrieving records. Records include a number of attributes about them — including assigned name for the application, a timestamp for when it was created, and data on their last usage such as, date and IP address. Each application password is also assigned a uuid for reference, in case you’d like to build infrastructure for additional properties and store them in an alternate location.

Getting Credentials

Generating Manually

From the Edit User page, you can generate new, and view or revoke existing application passwords. The form and the list table are both fully extensibleExtensible This is the ability to add additional functionality to the code. Plugins extend the WordPress core software. to allow for overloading to store additional data (more on this later, in “Authentication Scoping”).

The Application Passwords section of Edit User screen, after a new application password has been created.
The Edit User screen, after a new application password has been created.

Once a given password has been used, it will keep track of where and when it has been used – the “Last Used” column is accurate to within 24 hours (so that WordPress isn’t writing to the database on every usage — only if it’s a new day). This can be incredibly useful for identifying passwords that are no longer in use, so that they can be safely revoked.

Authorization Flow

To ensure that application password functionality is available, fire off a request to 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”) https://developer.wordpress.org/rest-api/. root URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org, and look at the authentication key in the response data. If this key is empty, then application passwords are not available (perhaps because the request is not over https:// or it has been intentionally disabled).

If, however, response.authentication is an object with a key of application-passwords it will offer a URL to send a user to complete the authentication flow. (You could just guess at the URL, but this gives us more of the relevant information in one go, as well as confirming that application passwords are available and enabled.)

The response.authentication['application-passwords'].endpoints.authorization url will likely look something like this:

https://example.com/wp-admin/authorize-application.php

Instead of just sending the user there to generate an application password, it would then be up to the user to reliably re-enter it into your application. So instead, some additional GET parameters are accepted along with the request:

  • app_name (required) – The human readable identifier for your app. This will be the name of the generated application password, so structure it like … “WordPress Mobile App on iPhone 12” for uniqueness between multiple versions.
    Whatever name you suggest can be edited by the user if they choose before the application is created. While you can choose to not pre-populate it for the user, it is required to create a password, so they will then be forced to create their own, and could select a non-intuitive option.
  • app_id (recommended) – a UUID formatted identifier. The app_id allows for identifying instances of your application, it has no special meaning in and of itself. As a developer, you can use the app_id to locate all Application Passwords created for your application.
    In the event of a data breach, your app_id could be distributed to void credentials generated with it, or if a site wants to allow only a given app_id or set of app_ids to register, this would enable that. However, it is strictly on the honor system — there is nothing to stop applications from generating new uuids with every authorization.
  • success_url (recommended) – The URL that you’d like the user to be sent to if they approve the connection. Three GET variables will be appended when they are passed back (site_url, user_login, and password); these credentials can then be used for 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. calls.
    If the success_url variable is omitted, a password will be generated and displayed to the user instead, to manually enter into their application.
  • reject_url (optional) – If included, the user will get sent there if they reject the connection. If omitted, the user will be sent to the success_url, with ?success=false appended to the end.
    If the success_url is omitted, the user just will be sent to their WordPress dashboard.
A screenshot of the new Authorize Application screen in the WP-Admin. A button is displayed to approve the connection, and one to reject the connection.
A screenshot of what the authorization flow will look like to a user.

As the parameters are all passed in via GET variables, if the user needs to log in first, they will all be preserved through the redirect parameter, so the user can then continue with authorization.

It is also worth noting that the success_url and redirect_url parameters will generate an error if they use a http:// rather than https:// protocol — however other application protocols are acceptable! So if you have a myapp:// link that opens your Android, iOS / MacOS, or Windowsthose will work!

Here is an example of a simple javascript application (under 100 lines of code) that uses this to authenticate to a WordPress site. Though not the tidiest code, it was created in under two hours one evening, but it goes through the proper flows and can make authenticated requests.

Programmatically through the REST API

If you have previously been using a different system to access the REST API and would prefer to switch over to using application passwords, it’s easy! You can generate yourself a new application password via a POST request to the new /wp/v2/users/me/application-passwords endpoint. Once you’ve got the new application password in the response data, you can delete any old credentials and just use the coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. implementation instead — but please consider using something like libsodium (which has a library bundled with WordPress alreadyhere’s an implementation example) or Vault to store the credentials encrypted, rather than in plaintext.

Using Credentials

REST API

The credentials can be passed along to REST API requests served over https:// using Basic Auth / RFC 7617, which is nearly ubiquitous in its availability — here’s the documentation for how to use it with cURL.

For a simple command-line script example, just swap out USERNAME, PASSWORD, and HOSTNAME in this with their respective values:

curl --user "USERNAME:PASSWORD" https://HOSTNAME/wp-json/wp/v2/users?context=edit

XML-RPC API

To use a generated application password with the legacy XML-RPC API, you can just use it directly in lieu of the account’s real password.

For a simple command-line script example, again just swap out USERNAME, PASSWORD, and HOSTNAME in this with their respective values:

curl -H 'Content-Type: text/xml' -d '<methodCall><methodName>wp.getUsers</methodName><params><param><value>1</value></param><param><value>USERNAME</value></param><param><value>PASSWORD</value></param></params></methodCall>' https://HOSTNAME/xmlrpc.php

Future 🔮 APIs

The application passwords authentication scheme can also be applied to future APIs for WordPress as they become available. For example, if GraphQL or other systems are enabled in WordPress, application passwords will provide them with a solid, established authentication infrastructure to build off of out of the box.

As an example of this, with a trivial code addition identifying whether the current load is an api request, WPGraphQL will now be able to accept authenticated requests without the need of an ancillary plugin, using just the application passwords functionality that has merged into core.

Using an Application Password on wp-login.php

You can’t. 😅 The point of application passwords are that they are to be used programmatically for applications, and not by humans for interactive sessions.

Feature Availability

By default, Application Passwords is available to all users on sites served over SSLSSL Secure Sockets Layer. Provides a secure means of sending data over the internet. Used for authenticated and private actions./HTTPSHTTPS HTTPS is an acronym for Hyper Text Transfer Protocol Secure. HTTPS is the secure version of HTTP, the protocol over which data is sent between your browser and the website that you are connected to. The 'S' at the end of HTTPS stands for 'Secure'. It means all communications between your browser and the website are encrypted. This is especially helpful for protecting sensitive data like banking information.. This can be customized using the wp_is_application_passwords_available and wp_is_application_passwords_available_for_user filters.

For example, to completely disable Application Passwords add the following code snippet to your site.

add_filter( 'wp_is_application_passwords_available', '__return_false' );

Without SSL, it is possible for the Application Password to be seen by an attacker on your networknetwork (versus site, blog) or the network between your site and the authorized application. If you are ok with this risk, you can force availability with the following code snippet.

add_filter( 'wp_is_application_passwords_available', '__return_true' );

If desired, it is possible to restrict what users on your site can use the Application Passwords feature. For example, to restrict usage to administrator users, use the following code snippet.

function my_prefix_customize_app_password_availability(
	$available,
	$user
) {
	if ( ! user_can( $user, 'manage_options' ) ) {
		$available = false;
	}

	return $available;
}

add_filter(
	'wp_is_application_passwords_available_for_user',
	'my_prefix_customize_app_password_availability',
	10,
	2
);

Future Development

Authentication Scoping

In future versions, the expectation is to include the ability to scope a given application password to limit its access. The intention is to work on building this in plugin-land until it’s ready for a core proposal.

What might password scoping look like? Here’s some methods being considered:

  • In a multisitemultisite Used to describe a WordPress installation with a network of multiple blogs, grouped by sites. This installation type has shared users tables, and creates separate database tables for each blog (wp_posts becomes wp_0_posts). See also network, blog, site environment, either restrict the credentials to a subset of the user’s blogs, or restrict it to only operate in a normal “blogblog (versus network, site) adminadmin (and super admin)” context, and not a “network admin” context.
  • Restrict functionality to only manage content — posts, pages, comments, custom post types — and disallow infrastructure management functionality like managing plugins, themes, and users.
  • Restrict the role that credentials can allow an application to operate as. For example, an Editor may restrict a set of credentials to only operate as though they had Author or Contributor permissions.

However this is done, implementing additional functionality to enforce the principle of least privilege on an application-by-application basis is a worthwhile expansion on the included functionality.

Fine-grained Capabilities

Right now, a user’s application passwords can be managed by any user who has permission to edit_user them. The ability to customize this behavior using a new set of more fine-grained capabilities is currently planned for 5.7.

Eventually Two-Factor Authentication?

Another useful bit of application passwords is that it will removes an obstacle for the inclusion of multi-factor authentication on interactive logins.

Previously, if you enabled an interactive step — whether captcha or second factor validation — on login pages, you would be in a bind with other non-interactive authentications, for example the legacy XML-RPC system. After all, if a bad actor can just brute force or use social engineering to discern the user’s password, it would be trivially usable via XML-RPC, where there is no ability to include an interactive prompt, and that functionality would need to be disabled entirely.

With that use case now being provided for via application passwords, there is additional flexibility for the normal browser-based wp-login.php system to evolve.

Further Resources

For bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. reports or enhancements, open a Trac ticket in the new App Passwords component with the rest-api focus.

Props @timothyblynjacobs, @m_butcher, @desrosj, @jeffmatson, for helping to write, review, and proofread.

#5-6, #application-passwords, #authentication, #core-passwords, #dev-notes, #rest-api, #two-factor

Registering default values for meta data

With WordPress 5.5, the register_meta() functions ( including register_post_meta() ) now support registering default values. Previously it was only possible to register default values for 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”) https://developer.wordpress.org/rest-api/. schema like this:

register_meta(
     'post',
     'greeting',
     array(
         'single'       => true,
         'type'         => 'string',
         'show_in_rest' => array(
             'schema' => array(
                 'type'  => 'string',
                 'default' => 'hello',
             ),
         ),
     )
 );

However, this would only be applied to calls made from within the REST API – calls to get_post_meta() would not use this default value. Now it is possible to pass in a default value that will be applied to all calls to any of 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. functions, like this:

register_meta(
     'post',
     'greeting',
     array(
         'single'       => true,
         'type'         => 'string',
         'default'      => 'hello',
         'show_in_rest' => array(
             'schema' => array(
                 'type'  => 'string',
             ),
         ),
     )
 );

This brings register_meta() inline with register_setting() where it has been possible to register default values for options since WordPress 4.7.

Default values can also be paired with object sub types (introduced in WordPress 4.9.8) to limit the scope of a default value. An example of this might be if 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 WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party registers a custom post typeCustom Post Type WordPress can hold and display many different types of content. A single item of such a content is generally called a post, although post is also a specific post type. Custom Post Types gives your site the ability to have templated posts, to simplify the concept. of product. A developer could register a default value that would only apply to the post of type product.

register_post_meta(
     'product',
     'price',
     array(
         'single'       => true,
         'type'         => 'string',
         'default'      => '0.00',
     )
 );

It is worth noting that registering a default value to a custom post type like this may have some performance overhead. To determine which post type the current post ID is, it has to load that object. See get_object_subtype for more detail. In most cases, the meta and main objects are loaded at the same time (like when using WP_Query) but if your code is doing something other than just loading meta data, it may now load the main object type too.

Non-Single Metadata

It is also possible to register not-single default values like this:

register_post_meta(
     'product',
     'price',
     array(
         'single'       => false,
         'type'         => 'string',
         'default'      => '0.00',
     )
 );

When requesting multiple values, like this:

$result = get_post_meta( 123, 'price', false );

The above code will return a numeric array with 0.00, as the first value.

Validation

When registering a default meta value the data must match the type provided. The following example will trigger a _doing_it_wrong notice as hello is not an integer.

register_meta(
     'post',
     'greeting',
     array(
         'single'       => true,
         'type'         => 'integer',
         'default'      => 'hello',
     )
 );

New filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output.

If you desire to do some really custom with default meta values, there is now a filter:

$value = apply_filters( "default_{$meta_type}_metadata", $value, $object_id, $meta_key, $single, $meta_type );

This is a dynamic filter, requiring you to add the meta type. This is an example of it’s use:

function add_my_meta_value( $value, $object_id, $meta_key, $single ){
	if( 'price' === $meta_key ) {
		if ( ! $single ) {
	   		$value = array( '0.99' );
		} else {
			$value = '0.99';
		}
	}
}
add_filter( 'default_post_metadata', 'add_my_meta_value', 10, 4 );

New functions

To make this functionality possible there are two new functions added to WordPress coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.:

  • get_metadata_raw()
  • get_metadata_default()

Now the get_metadata() function calls get_metadata_raw() and if the value is null, calls get_metadata_default(). So calling get_metadata() no longer gets the raw value, that is what get_metadata_raw() is now intended for.

More detail of the history of this change can be found in the original tracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. ticketticket Created for both bug reports and feature development on the bug tracker. #43941.

Props @timothyblynjacobs, @jjj and @justinahinon for reviewing prior to publish.

#5-5, #dev-notes, #rest-api

REST API changes in WordPress 5.5

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”) https://developer.wordpress.org/rest-api/. will see a lot of changes in WordPress 5.5. In an effort to explain each change adequately, a number of these were split out and covered by other dev notesdev note Each important change in WordPress Core is documented in a developers note, (usually called dev note). Good dev notes generally include a description of the change, the decision that led to this change, and a description of how developers are supposed to work with that change. Dev notes are published on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev notes is particularly important when plugin/theme authors and WordPress developers need to be aware of those changes.In general, all dev notes are compiled into a Field Guide at the beginning of the release candidate phase..

Below are some other noteworthy changes that deserve a call out.

Discoverable REST resource links

To aid automated and human discovery of the REST API, a link was added in the <head> of the document and as a Link headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. to the REST route for the currently queried document in [48273].

For example, in the <head> of this post, the following <link> appears.

<link rel="alternate" type="application/json" href="https://make.wordpress.org/core/wp-json/wp/v2/posts/82428">

Links are added for post, pages, and other custom post types, as well as terms and author pages. Links are not currently output for post archives or search results.

See #49116 for more information.

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.

Three new functions are introduced.

  • rest_get_route_for_post() retrieves the route for the given post. For instance, /wp/v2/posts/1.
  • rest_get_route_for_term() retrieves the route for the given term. For instance, /wp/v2/tags/1.
  • rest_get_queried_resource_route() retrieves the route for the currently queried resource. For instance, /wp/v2/users/1 when on the author page for the user with ID of 1.

All three functions return an empty string if a REST API route cannot be determined. To convert the route to a link, pass the result to the rest_url() function.

For more information, see #49116.

Customization

For custom post types, only routes using the built-in WP_REST_Posts_Controller controller class will work by default. For custom controllers, the rest_route_for_post filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. can be used to supply the correct route.

function my_plugin_rest_route_for_post( $route, $post ) {
	if ( $post->post_type === 'my-cpt' ) {
		$route = '/wp/v2/my-cpt/' . $post->ID;
	}

	return $route;
}
add_filter( 'rest_route_for_post', 'my_plugin_rest_route_for_post', 10, 2 );

Similar logic applies to taxonomies, only the built in WP_REST_Terms_Controller is supported by default. The rest_route_for_term filter can be used for custom controller classes.

function my_plugin_rest_route_for_term( $route, $term ) {
	if ( $term->taxonomy === 'my-tax' ) {
		$route = '/wp/v2/my-tax/' . $term->term_id;
	}

	return $route;
}
add_filter( 'rest_route_for_term', 'my_plugin_rest_route_for_term', 10, 2 );

The rest_get_queried_resource_route() function is filterable to allow for identification of custom resources.

function my_plugin_rest_queried_resource_route( $route ) {
	$id = get_query_var( 'my-route' );
	if ( ! $route && $id ) {
		$route = '/my-ns/v1/items/' . $id;
	}

	return $route;
}
add_filter( 'rest_queried_resource_route', 'my_plugin_rest_queried_resource_route' );

These links are output using the existing rest_output_link_wp_head and rest_output_link_header functions. As such, if API discovery has already been disabled, these links will not be rendered.

For more information, see #49116.

CORS changes

In [48112], the Link header was added to the list of exposed CORS response headers. Previously, only the X-WP-Total and X-WP-TotalPages headers were exposed which means that clients would have to manually construct the URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org to implement pagination instead of using the prev and next Links.

In [48452], the Content-Disposition, Content-MD5 and X-WP-Nonce were added to the list of allowed cors request headers. The Content-Disposition and Content-MD5 headers allow for easier file uploading across domains by using a File/Blob object directly. The X-WP-Nonce header is allowed for making authenticated cross-origin and same-origin requests consistently.

Two filters are introduced, rest_exposed_cors_headers and rest_allowed_cors_headers to simplify the process of plugins modifying the list of cors headers.

For more information, see #50369 and #41696.

Miscellaneous

Warn when omitting a permission_callback

The REST API treats routes without a permission_callback as public. Because this happens without any warning to the user, if the permission callback is unintentionally omitted or misspelled, the endpoint can end up being available to the public. Such a scenario has happened multiple times in the wild, and the results can be catastrophic when it occurs.

In [48526] a _doing_it_wrong notice has been added when a permission callback is omitted. For REST API routes that are intended to be public, it is recommended to set the permission callback to the __return_true built in function.

So for instance, this route registration will cause the following warning to appear.

register_rest_route(
	'my-ns',
	'echo',
	array(
		'methods'  => WP_REST_Server::EDITABLE,
		'callback' => function ( WP_REST_Request $request ) {
			return new WP_REST_Response( $request->get_param( 'echo' ) );
		},
	)
);
The REST API route definition for my-ns/echo is missing the required permission_callback argument. For REST API routes that are intended to be public, use __return_true as the permission callback.

If it was intended for this endpoint to be public, you could fix it like this.

register_rest_route(
	'my-ns',
	'echo',
	array(
		'methods'  => WP_REST_Server::EDITABLE,
		'callback' => function ( WP_REST_Request $request ) {
			return new WP_REST_Response( $request->get_param( 'echo' ) );
		},
		'permission_callback' => '__return_true',
	)
);

If you wanted to check the user’s capabilities, you could do it like this.

register_rest_route(
	'my-ns',
	'echo',
	array(
		'methods'  => WP_REST_Server::EDITABLE,
		'callback' => function ( WP_REST_Request $request ) {
			return new WP_REST_Response( $request->get_param( 'echo' ) );
		},
		'permission_callback' => function( WP_REST_Request $request ) {
			return current_user_can( 'manage_options' );
		},
	)
);

For more information, see #50075.

Using wp_send_json() is doing it wrong

When building a REST API route, it is important for the route callback (and permission_callback) to return data instead of directly sending it to the browser. This ensures that the additional processing that the REST API server does, like handling linking/embedding, sending headers, etc… takes place.

// This is incorrect.
echo wp_json_encode( $data );
die;

// And this.
wp_send_json( $data );

// And this.
wp_send_json_error( 'My Error' );

// Instead do this.
return new WP_REST_Response( $data );

// Or this.
return new WP_Error( 'my_error_code', 'My Error Message' );

After [48361], a _doing_it_wrong notice is now issued when any of the wp_send_json family of functions are used during a REST API request.

For more information, see #36271.

Preloading silenced deprecation errors

When using rest_preload_api_request any deprecation errors encountered when processing the request were silenced because the REST API handling took over as soon as the API server was booted.

After [48150], the REST API handling will only apply for actual REST API requests.

For more information, see #50318.

Props @desrosj and @justinahinon for proofreading.

#5-5, #dev-notes, #rest-api