Skip to content
WordPress8 min read

A Practical Deep Dive into the WordPress REST API

Beyond the basics of wp-json: designing custom endpoints, choosing an authentication approach, extending core responses and keeping the API fast.

The REST API is the part of WordPress that turns it from a website into a platform. It powers the block editor, mobile apps, headless front ends and most third-party integrations, yet plenty of developers only ever consume the default endpoints. Used deliberately, it is a solid foundation for custom application work; used carelessly, it leaks data and creates slow, chatty clients. This post covers the parts we use constantly in client projects.

The shape of the API

Everything lives under /wp-json, with core content at /wp-json/wp/v2: posts, pages, media, users, taxonomies and comments, each supporting filtering, pagination and embedding of related resources via the _embed parameter. The API is discoverable, so a GET to the index describes every registered route and its arguments, which makes exploring an unfamiliar site's API surface genuinely easy. Two request parameters do a lot of quiet work: _fields trims responses to only the properties you name, and _embed pulls featured images and terms into one round trip instead of three. Pagination totals arrive as X-WP-Total and X-WP-TotalPages response headers rather than in the body, which surprises almost every new consumer exactly once.

Custom endpoints done properly

The moment your front end needs data shaped differently from core responses, register a custom route rather than making clients stitch together multiple requests. Three things separate a production endpoint from a snippet: a real permission_callback, sanitised arguments, and a namespace with a version in it so you can evolve the contract later.

php
add_action( 'rest_api_init', function () {
    register_rest_route( 'strcli/v1', '/projects', array(
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'strcli_get_projects',
        'permission_callback' => '__return_true',
        'args'                => array(
            'per_page' => array(
                'default'           => 10,
                'sanitize_callback' => 'absint',
            ),
        ),
    ) );
} );

A permission_callback of __return_true is a deliberate declaration that the endpoint is public; for anything else, check capabilities properly with current_user_can inside the callback. Return WP_Error objects with sensible status codes on failure, and shape the response in the callback so the client receives exactly the fields it needs and nothing more.

Endpoints are contracts, so test them like contracts: permission behaviour for each role, validation of bad input, and the shape of both success and error responses. The schema you declare in args is not decoration either; it feeds the API's discoverable documentation and lets the server reject malformed requests before your callback ever runs.

Choosing an authentication approach

  • Cookie plus nonce: the right choice for JavaScript running inside the WordPress front end or admin, and what the block editor itself uses.
  • Application passwords: built into core, ideal for server-to-server integrations; each integration gets its own revocable credential.
  • JWT or OAuth via a plugin: appropriate for mobile apps and decoupled front ends where users log in from outside WordPress.

Whichever you choose, scope it. An integration that only reads published content should authenticate as a user with a role that can only read published content, not as an administrator whose password happens to be handy. And rotate integration credentials when staff leave, exactly as you would passwords.

Extending core responses instead of replacing them

Often you do not need a custom route, just one extra field on an existing resource. register_rest_field adds properties to core responses with proper get and update callbacks, and registering meta with show_in_rest exposes it with schema validation included. This keeps you inside core's caching, pagination and permission model rather than reinventing it.

Keeping it fast

REST responses are dynamic PHP like any other request, so they benefit from the same layers: a persistent object cache underneath, and full response caching at the edge for public GET endpoints, keyed carefully and purged on publish. Watch payload sizes, avoid N-plus-one patterns inside callbacks, and resist per-request external HTTP calls in an endpoint, which multiply every client's latency by your slowest dependency. Treat the API as a product surface with the same care you give templates, and it will carry surprisingly heavy loads.

Building something on top of the WordPress REST API and want it reviewed before launch? That is a conversation STRCLI is always glad to have.

Start your project

Have an idea? Let's ship it together.

Tell us what you're building — we'll reply within one business day with an honest take and a clear next step.