In the context of the WordPress REST API a route is a URI which can be mapped to different HTTP methods. The mapping of an individual HTTP method to a route is known as an endpoint.
The Route is the URL path itself The Endpoint is the combination of a Route and an HTTP Method (like GET, POST, or DELETE).
/wp-json/ is a route, and when that route receives a GET request then that request is handled by the endpoint which displays what is known as the index for the WordPress REST API.
A REST API request is represented within WordPress by an instance of the WP_REST_Request class, which is used to store and retrieve information for the current request.
he WP_REST_Response class provides a way to interact with the response data returned by endpoints
Each endpoint requires a particular structure of input data, and returns data using a defined and predictable structure. Those data structures are defined in the API Schema.
Controller classes unify and coordinate all these various moving parts within a REST API response cycle. With a controller class you can manage the registration of routes & endpoints, handle requests, utilize schema, and generate API responses.
In the world of REST APIs, HAL (Hypertext Application Language) is a specific standard for defining how to represent hypermedia links within your JSON or XML responses.
Think of it as the “instruction manual” that tells a client not just what the data is, but where they can go next.
{
// example : _links
"_links": {
"self": { "href": "/orders/123" },
"next": { "href": "/orders/124" },
"customer": { "href": "/customers/654" }
},
"order_id": 123,
"total": 50.00,
"status": "shipped"
}
// example: _embedded
{
"_links": {
"self": { "href": "/orders/123" }
},
"total": 50.00,
"_embedded": {
"items": [
{
"_links": { "self": { "href": "/items/abc" } },
"name": "Widget",
"price": 25.00
}
]
}
}
Using a standard like HAL moves your API toward the highest level of REST Maturity (often called HATEOAS: Hypermedia as the Engine of Application State).
| Benefit | Description |
|---|---|
| Decoupling | The client doesn’t need to “guess” URLs; it just follows the links provided by the server. |
| Discoverability | Developers can explore the API by simply clicking through the JSON links in a browser or tool. |
| Consistency | It provides a predictable pattern for every endpoint, making it easier for teams to build and consume services. |
When using HAL, the Content-Type header should technically be application/hal+json. |
The preferred way to handle discovery is to send a HEAD request to the supplied address. The REST API automatically adds a Link header to all front-end pages that looks like the following:
Link: <http://example.com/wp-json/>; rel="https://api.w.org/"
Pretty Permalinks
| Type | URL Example | Description |
|---|---|---|
| Ugly (Query) | example.com/?p=105 | Uses a database ID. Hard to remember and tells you nothing about the content. |
| Pretty | example.com/blog/how-to-bake-cake | Uses a “slug” (keywords). Easy to read and great for SEO. |
Example
add_action( 'rest_api_init', function () {
register_rest_route( 'my-plugin/v1', '/latest-posts/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'my_custom_get_post_handler',
'permission_callback' => '__return_true', // Allow public access
) );
} );
/**
* @param WP_REST_Request $request Full data about the request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
function my_custom_get_post_handler( $request ) {
// --- 1. Working with WP_REST_Request ---
// Extract the 'id' parameter from the URL path defined in register_rest_route
$post_id = $request->get_param( 'id' );
// You can also get headers or body data
// $token = $request->get_header( 'Authorization' );
$post = get_post( $post_id );
if ( empty( $post ) ) {
return new WP_Error( 'no_post', 'Post not found', array( 'status' => 404 ) );
}
// Prepare data for the response
$data = array(
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
'slug' => get_post_field( 'post_name', $post_id ),
);
// --- 2. Working with WP_REST_Response ---
$response = new WP_REST_Response( $data );
// You can manually set the HTTP status code (e.g., 200 OK)
$response->set_status( 200 );
// You can add custom headers or HAL-style links
$response->add_link( 'self', rest_url( 'my-plugin/v1/latest-posts/' . $post_id ) );
// This is how you manually inject the "_links" section
$response->add_link( 'self', rest_url( 'my-plugin/v1/resource/101' ) );
$response->add_link( 'collection', rest_url( 'my-plugin/v1/resource' ) );
$response->add_link( 'author', rest_url( 'wp/v2/users/1' ), array( 'embeddable' => true ) );
return $response;
}
www.example.com/wp-json wordpress rest namespace. It is suffixed with plugin specific namespace to differentiate.
/v2/posts is for default posts