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.