The REST API
Since version 4.7, WordPress has shipped with a built-in REST API that exposes posts, pages, users, comments, and more as JSON, over plain HTTP endpoints. This turns WordPress from "a system that only renders its own PHP templates" into a data source that any application — written in any language — can read from and write to.
1. Built-In Endpoints
Every WordPress site automatically exposes endpoints under /wp-json/wp/v2/. Visiting
one directly in a browser shows raw JSON — for example, requesting all published posts:
GET https://example.com/wp-json/wp/v2/posts
conceptually returns an array of post objects shaped like this:
[
{
"id": 42,
"date": "2026-08-01T10:00:00",
"title": { "rendered": "Hello World" },
"content": { "rendered": "<p>Post body HTML</p>" },
"link": "https://example.com/hello-world"
}
]
Similar endpoints exist for pages (/wp-json/wp/v2/pages), users, categories, tags,
and media — the entire admin's core data model is reachable as JSON with no extra setup.
2. Why This Matters: Headless WordPress
Because content is available as plain JSON, WordPress can be used purely as a content backend — editors keep using the familiar WordPress admin to write posts, while the actual public-facing site is a completely separate frontend built in React, Vue, or any other framework, fetching content over the REST API. This pattern is called headless WordPress. The REST API is also what powers the block editor (Gutenberg) itself — every block save and load goes through these same endpoints.
3. Registering a Custom REST Endpoint
Beyond the built-in endpoints, plugins can expose their own custom routes with
register_rest_route(), hooked to the rest_api_init action. This is
useful whenever a plugin manages data that doesn't fit the built-in post/page model.
<?php
function myplugin_register_routes() {
register_rest_route('myplugin/v1', '/books', [
'methods' => 'GET',
'callback' => 'myplugin_get_books',
'permission_callback' => '__return_true',
]);
}
add_action('rest_api_init', 'myplugin_register_routes');
function myplugin_get_books($request) {
$books = [
['id' => 1, 'title' => 'PHP Basics'],
['id' => 2, 'title' => 'WordPress Development'],
];
return new WP_REST_Response($books, 200);
}
// Now reachable at: /wp-json/myplugin/v1/books
myplugin/v1 is the route's namespace — always version it (v1, v2...)
so future changes don't break existing consumers. The permission_callback is
required as of WordPress 5.5+; returning __return_true makes the endpoint public,
while a real capability check would restrict who can call it.
show_in_rest => true, as shown in Custom Post Types & Taxonomies, is automatically exposed at its own /wp-json/wp/v2/ endpoint too — no extra route registration needed.4. Authentication Basics
Read access to public content needs no authentication at all — that's why the examples above work with a plain GET request. Writing data, or reading private content, does need authentication, and WordPress offers a few layers:
- Cookie authentication — works automatically for logged-in users inside the WordPress admin (e.g. the block editor calling the REST API behind the scenes)
- Application Passwords — a built-in feature (since WP 5.6) letting a user generate a dedicated password just for API access, ideal for external scripts and apps
- Third-party auth plugins — such as JWT authentication plugins, useful for headless setups needing token-based auth
5. Calling the REST API from JavaScript
Consuming these endpoints from the frontend is just an ordinary HTTP request. Using the
browser's built-in fetch():
fetch('https://example.com/wp-json/wp/v2/posts')
.then(response => response.json())
.then(posts => {
posts.forEach(post => {
console.log(post.title.rendered);
});
});
This single pattern — fetch JSON, render it however you like — is the entire foundation a headless frontend is built on.