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 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.