Crafting a Secure Custom WordPress REST Endpoint for Event Data
One of the most powerful features for modern WordPress infrastructure engineers is the REST API, and moving beyond core endpoints with custom routes opens up a world of possibilities for decoupled applications, integrations, and internal tooling. Today, I want to walk through designing a custom REST endpoint for a theoretical "Events" plugin, focusing on how to expose data securely and efficiently, ensuring only authorized users can access sensitive information like draft events.
Imagine our plugin manages a custom post type called `event`. We want an endpoint to fetch details for a single event, but only authenticated users should be able to see events that are still in a `draft` status. First, we need to register our custom route. This typically happens within a plugin's main file or a dedicated API class, hooked into `rest_api_init`.
```php add_action( 'rest_api_init', function () { register_rest_route( 'my-events/v1', '/event/(?P<id>\d+)', [ 'methods' => 'GET', 'callback' => 'my_events_get_single_event', 'permission_callback' => 'my_events_get_single_event_permissions', 'args' => [ 'id' => [ 'validate_callback' => function( $param, $request, $key ) { return is_numeric( $param ); } ], ], ] ); } ); ```
Here, `my-events/v1` is our namespace, preventing conflicts. The route `/event/(?P<id>\d+)` captures an event ID, which we validate to ensure it's numeric. The magic happens in the `callback` and `permission_callback` functions.
Let's tackle permissions first. This function determines who can even *attempt* to access the endpoint. For our use case, we'll allow anyone to hit the endpoint, but restrict *what* they can see based on authentication.
```php function my_events_get_single_event_permissions( WP_REST_Request $request ) { // For this endpoint, we'll allow public access, but the callback will handle data visibility based on authentication. // If you wanted to entirely restrict access, you'd return `is_user_logged_in()` here. return true; } ```
Now for the `callback` function, where we fetch and prepare our data. This function receives the `WP_REST_Request` object, allowing us to access parameters like our `id`.
```php function my_events_get_single_event( WP_REST_Request $request ) { $event_id = (int) $request['id']; $event = get_post( $event_id );
if ( ! $event || 'event' !== $event->post_type ) { return new WP_Error( 'rest_event_not_found', 'Event not found.', [ 'status' => 404 ] ); }
// Handle visibility for draft events if ( 'draft' === $event->post_status ) { if ( ! is_user_logged_in() || ! current_user_can( 'edit_post', $event_id ) ) { return new WP_Error( 'rest_cannot_view_draft', 'You are not authorized to view this draft event.', [ 'status' => 403 ] ); } }
// Prepare event data for the response $data = [ 'id' => $event->ID, 'title' => $event->post_title, 'description' => apply_filters( 'the_content', $event->post_content ), // Filter content for formatting 'status' => $event->post_status, 'permalink' => get_permalink( $event->ID ), // Add more custom fields or metadata here ];
return rest_ensure_response( $data ); } ```
In the callback, we fetch the `event` post. We perform critical checks: 1. Does the event exist and is it of the correct post type? If not, a `404 Not Found` error is returned. 2. If the event's `post_status` is `draft`, we then check if the current request is authenticated (`is_user_logged_in()`) AND if the user has the capability to `edit_post` for that specific event. If either fails, a `403 Forbidden` error is returned. This is where our security logic for sensitive draft data resides. 3. Finally, if all checks pass, we construct an array of relevant data points from the `WP_Post` object and potentially custom fields. `rest_ensure_response()` wraps our data, ensuring it's properly formatted for the REST API.
This pattern provides a robust way to expose specific data points from your WordPress application, giving you granular control over what data is accessible and under what conditions. Leveraging `register_rest_route`, `permission_callback`, and `current_user_can` alongside standard WordPress authentication mechanisms allows you to build powerful and secure APIs that truly extend WordPress beyond its traditional role. This approach is fundamental for anyone building headless WordPress sites or integrating WordPress with other backend systems.