Skip to main content
Crafting a Secure REST Endpoint for Your WordPress Product Catalog

Crafting a Secure REST Endpoint for Your WordPress Product Catalog

When building sophisticated applications atop WordPress, the default REST API, while powerful, doesn't always provide the exact data structure or access…

When building sophisticated applications atop WordPress, the default REST API, while powerful, doesn’t always provide the exact data structure or access control needed. You might require a highly specific endpoint that filters data, aggregates custom fields, or enforces unique authentication. Today, I’ll demonstrate creating a secure, custom REST API endpoint for a hypothetical product catalog, exposing only “available” products and requiring a specific API key.

Let’s assume a custom post type product, with each having a custom field _is_available (true/false). We need an endpoint returning only available products, perhaps for an external system, secured by an API key.

First, we register our custom route using register_rest_route. This snippet typically lives in your theme’s functions.php or a custom plugin. We use a unique namespace and define the endpoint’s path and methods.

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-catalog/v1', '/products/available', [
        'methods'             => 'GET',
        'callback'            => 'my_catalog_get_available_products',
        'permission_callback' => 'my_catalog_permission_check',
        'args'                => [
            'api_key' => [
                'sanitize_callback' => 'sanitize_text_field',
                'required'          => true,
            ],
        ],
    ] );
} );

The permission_callback and api_key argument are crucial for security. The permission_callback runs before our main callback and dictates authorization. If it returns false or a WP_Error, access is denied.

Now, let’s implement the permission_callback. For this example, we’ll store a master API key in wp-config.php via define( 'MY_CATALOG_API_KEY', 'your_super_secret_key' );.

function my_catalog_permission_check( WP_REST_Request $request ) {
    $provided_api_key = $request->get_param( 'api_key' );
    $master_api_key   = defined( 'MY_CATALOG_API_KEY' ) ? MY_CATALOG_API_KEY : '';

if ( empty( $provided_api_key ) || empty( $master_api_key ) ) {
        return new WP_Error( 'rest_missing_api_key', 'API key is missing or not configured.', [ 'status' => 401 ] );
    }

if ( $provided_api_key !== $master_api_key ) {
        return new WP_Error( 'rest_invalid_api_key', 'Invalid API key provided.', [ 'status' => 403 ] );
    }

return true; // Access granted
}

This function compares the request’s api_key parameter to our defined master key. A mismatch, or missing key, results in a WP_Error object, which WordPress automatically converts to a JSON error response with an appropriate HTTP status.

Finally, the my_catalog_get_available_products callback fetches our products. We use WP_Query to retrieve all published product posts where _is_available is true.

function my_catalog_get_available_products( WP_REST_Request $request ) {
    $args = [
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'meta_query'     => [
            [
                'key'   => '_is_available',
                'value' => 'true',
                'compare' => '=',
            ],
        ],
    ];

$products_query = new WP_Query( $args );
    $response_data  = [];

if ( $products_query->have_posts() ) {
        while ( $products_query->have_posts() ) {
            $products_query->the_post();
            $product_id = get_the_ID();
            $product_data = [
                'id'          => $product_id,
                'title'       => get_the_title(),
                'permalink'   => get_permalink(),
                'description' => get_the_excerpt(),
                'price'       => get_post_meta( $product_id, '_product_price', true ),
                'sku'         => get_post_meta( $product_id, '_product_sku', true ),
            ];
            $response_data[] = $product_data;
        }
        wp_reset_postdata();
    }

return new WP_REST_Response( $response_data, 200 );
}

Here, we query, iterate, and manually build an array, extracting specific fields and custom meta like _product_price and _product_sku. This offers precise control over exposed data and its format.

To test, make a GET request to yourdomain.com/wp-json/my-catalog/v1/products/available?api_key=YOUR_MASTER_API_KEY. Without the correct key, you’ll receive an error. Building custom REST endpoints like this empowers you to tailor your WordPress backend to external application needs, providing fine-grained control over data and security—a fundamental skill for advanced WordPress infrastructure.