Felipe Elia

ENPT

The WordPress REST API

13 min read

August 2026 update: the original Portuguese version of this post was published in 2019. I have now revised the parts that have aged, mainly authentication and endpoint registration, and used that updated article as the basis for this English version.

One of my goals with this blog is to demystify WordPress subjects that, even when they are simple, somehow get sold as extremely complicated. The WordPress REST API is one of them.

This is a long post, but by the time you finish it you will understand everything about the WP REST API, so grab a snack and keep reading!

Before getting into names and concepts, it is better to see what the WordPress REST API actually is. It is simple, and it has been part of WP core since version 4.7. Install WP without any plugins and open http://localhost/?rest_route=/wp/v2/posts in your browser. You will get some JSON. Throw that pile of code into a tool like JSON Formatter, or look at the Network tab in your browser’s developer tools, and it will look something like this:

JSON
[
  {
    "id": 1,
    "date": "2019-07-05T09:23:31",
    "date_gmt": "2019-07-05T12:23:31",
    "guid": {
      "rendered": "http://localhost/?p=1"
    },
    "modified": "2019-07-05T09:23:31",
    "modified_gmt": "2019-07-05T12:23:31",
    "slug": "hello-world",
    "status": "publish",
    "type": "post",
    "link": "http://localhost/hello-world/",
    "title": {
      "rendered": "Hello world!"
    },
    "content": {
      "rendered": "\n<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n",
      "protected": false
    },
    "excerpt": {
      "rendered": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n",
      "protected": false
    },
    "author": 1,
    "featured_media": 0,
    ...
    "categories": [
      1
    ],
    "tags": [],
    ...
  }
]

If you are using pretty permalinks (under Settings → Permalinks in the Dashboard), you can open http://localhost/wp-json/wp/v2/posts and get the same result.

What is happening here?

When you typed that address and pressed Enter, your browser sent a request to the server (localhost, in this case) through HTTP, using the GET method—also called a verb. Yes, the same GET we put in the method attribute of a form. In short: an HTTP GET request. You use this every day because this is how the web works. Technically, it is how the World Wide Web works — the internet is the network connecting the computers — but that difference is a subject for another post.

How to use the WordPress REST API

Because the REST API is based on client/server communication, there are two scenarios, depending on which side you are on. As the client, you can use the API with any programming language that can communicate over HTTP. As the server, there is a good chance WordPress already provides what you need. If it does not, you can create a new endpoint.

Client: consuming data from external WP sites

If you want to consume REST API data from another WordPress installation, all you have to do is… send an HTTP GET request. WP has a few native functions that make this pretty easy. The code below uses wp_remote_get() to fetch the latest posts from WordPress.org News. Using one WordPress site to consume another WordPress site’s REST API is just convenient here. In the real world, this code could be written in any language or framework.

PHP
$response = wp_remote_get(
    'https://wordpress.org/news/wp-json/wp/v2/posts?per_page=3'
);

if (
    is_wp_error( $response ) ||
    200 !== wp_remote_retrieve_response_code( $response )
) {
    return;
}

$posts = json_decode( wp_remote_retrieve_body( $response ) );

if ( ! is_array( $posts ) ) {
    return;
}

echo '<ul>';
foreach ( $posts as $rest_post ) {
    printf(
        '<li><a href="%1$s">%2$s</a></li>',
        esc_url( $rest_post->link ),
        esc_html( wp_strip_all_tags( $rest_post->title->rendered ) )
    );
}
echo '</ul>';

Client: sending data to external WP sites

If you need to send data that will be stored on the external WordPress site, there is one extra step. This happens for two reasons:

  • You need to prove that you are a WP user with permission to do it, and
  • GET is used to retrieve data. To send data, you need another verb/method, such as POST.

When the request happens inside WordPress and the user is already logged in, the standard authentication method uses cookies and a nonce. For an external application, WordPress has included native Application Passwords since version 5.6. This is a separate password created only for the integration, and you can revoke it without changing the user’s main password.

You create an Application Password in the user’s profile inside the WordPress Dashboard. The request uses HTTP Basic Auth, but with this specific password instead of the account’s normal password. There is no negotiation here: outside your local only do this over HTTPS, and keep the credential out of your code.

Once the Application Password is ready, you can use code like this to create a new post. Note the authentication header and the native wp_remote_post() function. Notice that I passed draft in the status attribute: the post is created as a draft instead of being published immediately. The complete list of values accepted in the request body (the body parameter) is in the official documentation.

PHP
$username             = 'felipe';
$application_password = 'xxxx xxxx xxxx xxxx xxxx xxxx';

$response = wp_remote_post(
    'https://example.com/wp-json/wp/v2/posts',
    array(
        'headers' => array(
            'Authorization' => 'Basic ' . base64_encode(
                $username . ':' . $application_password
            ),
        ),
        'body'    => array(
            'title'   => 'My new post',
            'content' => 'Post content',
            'status'  => 'draft',
        ),
    )
);

I left the Application Password in a variable to keep the example easy to understand. In a real project, it should come from an environment variable or another secure configuration. If the integration stops existing or the credential leaks, just revoke that password from the user’s profile.

Server: exposing data from your WordPress site to external sites

By default, WordPress already provides many endpoints (see Concepts and definitions below), including the now-documented /wp/v2/search endpoint. To enable the REST API for a custom post type, add 'show_in_rest' => true to the arguments passed to register_post_type(). If another plugin or the theme registers the CPT (even though that belongs in a plugin), you can change its arguments with the register_post_type_args filter.

If you need something beyond what WordPress provides, use the rest_api_init action and the register_rest_route() function. This documentation page explains the process step by step, but the short version is that you will write code like the example below and implement the callback responsible for receiving and/or sending the data. The parameter accepts any callable.

PHP
add_action(
    'rest_api_init',
    function () {
        register_rest_route(
            'myplugin/v1',
            '/author/(?P<id>\d+)',
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => 'myplugin_get_author',
                'permission_callback' => function () {
                    return current_user_can( 'list_users' );
                },
                'args'                => array(
                    'id' => array(
                        'sanitize_callback' => 'absint',
                    ),
                ),
            )
        );
    }
);

Also note the permission_callback. Since WordPress 5.5, it must be present when you register a route. In this example, only users with the list_users capability can access the endpoint. If the route is genuinely public, you can use __return_true—but do that because the data can be public, not just to make the warning disappear.

The first argument of register_rest_route() is the namespace, and the second is the route. Let’s move on to Concepts and definitions, because knowing what each of these things means makes the documentation much easier to understand.

REST concepts and definitions

Starting at the beginning, to understand the REST API, we first need to understand what an interface is (the I in API).

What is an interface?

An interface is the external layer of something that accepts inputs and provides outputs. Yes, that is an extremely broad concept, but that is really it. Your keyboard, monitor, site screens, and the electrical outlets in your house are all examples of interfaces.

The concept of an interface is directly related to the idea of a black box: it does not matter how something is implemented, as long as a given input produces the expected output.

The color of the wires carrying electricity to an outlet does not matter; what matters is that it works when you plug something in. In the same way, the implementation behind a screen does not matter to the person using it; what matters is that the expected thing happens when a button is pressed.

The “does not matter” part only applies to the black-box concept, okay? As a WordPress professional, how WP implements things should matter—a lot.

And what is an API?

API stands for Application Programming Interface. In this case, expanding the acronym doesn’t make it more useful, does it? This means your WordPress site is an application with an interface accessible via programming.

When you call a native WordPress function, it “does not matter” how that function was implemented; what matters is that it does what you expect. If I call wp_insert_post() with the right parameters, I am not interested in how WP processes it. I just want to be sure the post will be created. Through programming, I provide an input and expect the correct output.

There is another post on this blog with more details about what an API is.

What is REST?

If you have ever watched a cartoon, you know the R in R.I.P. stands for rest. The REST in REST API has absolutely nothing to do with that (which is one reason to keep writing it in uppercase).

Here, REST stands for Representational State Transfer. If spelling out API did not help much, this one probably helps even less.

Here is the explanation: given a resource (posts, pages, users, categories, and so on), we create a textual representation of that resource’s current state. A post title and content, or a user’s name, are attributes that form this “current state.” Put them in JSON, and you have a textual representation. REST is the transfer of these representations of a resource’s state or, as the name says, its representational state.

And RESTful?

You already know words such as peaceful, helpful, painful, and careful. The -ful suffix describes a quality: something helpful provides help, something peaceful has peace, and so on.

RESTful simply describes something that implements the REST architecture. When an API follows those principles, we call it RESTful. One of those terms that sounds much more mysterious than it is.

Be careful not to write REST full or RESTfull. Full means complete or filled, and it has nothing to do with what we are talking about here.

Namespaces, routes, endpoints, and schemas

The documentation has a glossary, because you may run into a few unfamiliar words while developing. I will explain some of them here to make things easier. If I missed one, let me know, and I will update the post.

Namespace

In this context, a namespace is a string that separates features into groups. The default namespace is wp/v2, WooCommerce currently uses wc/v3, and you could use client/v1 or plugin/v1, changing v1 as the code evolves and new versions are released. It is the first parameter you pass when registering a new route with register_rest_route().

Routes and endpoints

A route is the second parameter passed to register_rest_route(). It is the string that exposes a feature and may have one or more endpoints associated with it.

An example from the documentation is https://example.com/wp-json/wp/v2/posts/123. It contains a single route, wp/v2/posts/123. Depending on the verb or method used in the request, this same route has three endpoints:

  • GET returns the post data through the get_item method;
  • POST or PUT updates a post through the update_item method; and
  • DELETE removes the post through the delete_item method.

In other words, the same route exposes three endpoints, and the one you access depends on the method or verb used in the request. You can see more by looking at the register_routes() method in the WP_REST_Posts_Controller class.

I used wp_remote_get() and wp_remote_post() in this post, but you can use wp_remote_request() and pass any verb through the method parameter.

Schema

A schema represents the data that forms the API response. The Posts schema says that fields such as id, title, content, and author will be part of the response. This information is useful both for people building an API integration and for automated integrations.

To access the schema, send a request to the address using the OPTIONS method. Browsers are great for GET requests, but testing these more “unusual” verbs is not always so easy. For that, I recommend Postman, available for Linux, Windows, and Mac. It gives you a simple way to format the request however you need, changing the verb, body, and headers.


Want to get the next posts? Subscribe to the newsletter below.

Enjoyed this post? Get the next one by email.

New articles and useful tools, whenever there’s something worth sharing.

No fixed schedule. No spam. Unsubscribe anytime.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.