f in x
WordPress REST API: Custom Endpoints and Authentication – A Practical Guide
> cd .. / HUB_EDITORIALE
Analisi dei dati e metriche

WordPress REST API: Custom Endpoints and Authentication – A Practical Guide

[2026-06-11] Author: Meteora Web Redazione

The Problem: The default API doesn't do what you need

You have a React app, a mobile app, or an external system that needs to talk to your WordPress site. The standard WP routes for posts, pages, users are there, but your requirements are different: you need to expose custom metadata, trigger business logic, or integrate a custom plugin. Or you want only authenticated users to read or write certain data. The WordPress REST API gives you all this, but you need to know how to use it well. At Meteora Web, we do this every day: from WooCommerce sites with custom shipping calculations to B2B portals with custom roles. Here's how.

Basics: Registering a custom route

To create a custom endpoint, use the register_rest_route function. It should be called inside the rest_api_init hook. Put the code in a plugin or in your theme's functions.php (a plugin is better for modularity). Here's a concrete example: an endpoint that returns top-selling products (if you're on WooCommerce).

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/top-products/', array(
        'methods'  => 'GET',
        'callback' => 'my_get_top_products',
        'permission_callback' => '__return_true',
    ) );
} );

function my_get_top_products( $data ) {
    $products = wc_get_products( array( 'orderby' => 'total_sales', 'order' => 'DESC', 'limit' => 5 ) );
    $response = array();
    foreach ( $products as $product ) {
        $response[] = array(
            'id'    => $product->get_id(),
            'name'  => $product->get_name(),
            'price' => $product->get_price(),
            'url'   => get_permalink( $product->get_id() ),
        );
    }
    return new WP_REST_Response( $response, 200 );
}

Quick explanation: the namespace my-plugin/v1 avoids conflicts. The method is GET, the callback is the function that runs the logic. permission_callback here is public, but in reality you'll want to protect it. The $data object contains request parameters (query params, body, etc.).

What to do right now

Try creating an endpoint that returns the list of tags for a post. Use register_rest_route with a parameter {id} — for example /my-plugin/v1/post/(?P\d+)/tags. Then call it from your browser with /wp-json/my-plugin/v1/post/42/tags.

Authentication: why just __return_true is not enough

If your endpoint modifies data (POST, PUT, DELETE) or exposes sensitive information, you must authenticate the request. WordPress has three main methods: Cookie authentication (default for logged-in users), Nonce (for JavaScript applications on the same domain), OAuth 1.0a or JWT (for external apps). We often use nonces for WordPress plugins with a React frontend running on the same domain, and JWT for mobile or multi-domain apps.

Nonce: the simplest (and underestimated) method

Nonces are temporary tokens that verify the origin of the request. They only work for logged-in users. Here's how to integrate them:

// In your plugin, register the route
register_rest_route( 'my-plugin/v1', '/update-price/(?P\d+)', array(
    'methods'  => 'POST',
    'callback' => 'update_price_callback',
    'permission_callback' => function () {
        // Similar to check_admin_referer but for REST
        return current_user_can( 'edit_posts' ) && wp_verify_nonce( $_SERVER['HTTP_X_WP_NONCE'] ?? '', 'wp_rest' );
    },
) );

function update_price_callback( $request ) {
    $id = $request['id'];
    $new_price = $request->get_param( 'price' );
    // ... update logic
    return new WP_REST_Response( [ 'success' => true ], 200 );
}

In the frontend JavaScript (React or vanilla), pass the X-WP-Nonce header using the nonce generated by wp_localize_script:

// In functions.php
wp_localize_script( 'my-script', 'wpApiSettings', array(
    'root' => esc_url_raw( rest_url() ),
    'nonce' => wp_create_nonce( 'wp_rest' ),
) );

Then in your fetch:

fetch( wpApiSettings.root + 'my-plugin/v1/update-price/123', {
    method: 'POST',
    headers: {
        'X-WP-Nonce': wpApiSettings.nonce,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify( { price: 29.99 } ),
} );

JWT for mobile apps and external clients

When the app is not on the same domain (e.g., React Native, Flutter, or a Node.js service), nonces don't work because they rely on session cookies. You need a JWT token. Install the plugin JWT Authentication for WP REST API (or similar) and configure it. Note: it requires modifying rewrite rules and setting a secret key in wp-config.php. The flow is simple: the user logs in at /wp-json/jwt-auth/v1/token with username and password, gets a token, and sends it with every request as Authorization: Bearer <token>. Here's a permission_callback example for JWT protected routes:

// Assuming the JWT plugin is active
add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/sensitive-data', array(
        'methods'  => 'GET',
        'callback' => 'sensitive_data_callback',
        'permission_callback' => function () {
            // The JWT plugin already handles authentication and sets $current_user
            // Just check that the user is logged in
            return is_user_logged_in();
        },
    ) );
} );

Caution: The JWT plugin for WP requires some steps — enable permalinks, set JWT_AUTH_SECRET_KEY in wp-config, and handle token refresh (tokens expire). If you want more control, you can build your own middleware to verify the token, but we recommend starting with a battle-tested plugin.

Common mistakes and how to avoid them

Never return WP_Error directly

If you use return new WP_Error( 'code', 'Message' );, WordPress will automatically transform it into a JSON response with status 500. Better to use WP_REST_Response with the appropriate status.

Namespace and permission_callback

Don't forget to declare permission_callback. If you don't, WordPress returns a 403 error by default. For public endpoints use 'permission_callback' => '__return_true'.

Route caching

WordPress caches registered routes. If you modify the code, deactivate and reactivate the plugin, or go to Settings -> Permalinks and save again.

When to use a custom endpoint? A real case

One of our clients, a clothing store (yes, we managed their ERP), wanted a mobile app for staff to read stock levels and prices in real time. WooCommerce's default API was fine for reading, but quick modifications (last-minute discounts) needed a custom endpoint with business logic (e.g., apply discount only if the product is on sale). We created JWT-protected routes that the app could use without exposing admin credentials. Another case: a booking platform with Vue.js talking to a headless WordPress — nonce authentication was perfect because everything ran on the same domain.

In summary – what to do next

  1. Choose your authentication method: nonce for integrated frontend, JWT for external clients, cookies for admin.
  2. Register your first protected route: start with a simple example (e.g., GET that returns data for the logged-in user).
  3. Test with Postman or cURL: verify that authentication works and permissions are correct.
  4. Secure it: don't forget to validate and sanitize input parameters (sanitize_text_field, absint).
  5. Document your endpoint: add a paragraph in the plugin's README for future developers (or for yourself in six months).

If you need a complex headless architecture or integration with external services, contact us. We work on these topics every day.

Sponsored Protocol

Meteora Web Redazione

> AUTHOR_EXTRACTED

Meteora Web Redazione

La redazione di Meteora Web: notizie, analisi e aggiornamenti quotidiani dal mondo del digitale, dei social e dell'intelligenza artificiale.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()