Are you trying to add a custom endpoint and/or route to the WordPress REST API?
Are you then stuck in a rut if its missing pagination?
Have no fear, I got your back! (full code is the bottom before that are small explanations explaining what’s going on)
Adding a custom endpoint to WordPress REST API
We are going to start with extending WP_REST_CONTROLLER
class REFINED_POST_ENDPOINT extends WP_REST_Controller { } add_action( 'rest_api_init', function () { $controller = new REFINED_POST_ENDPOINT(); $controller->register_routes(); } );
In REFINED_POST_ENDPOINT
is where we’ll place code specifics and the add_action
is what will run all the little magical things we do (you added code, and route, this will run it).
In our newly created class we will start with the following constructor:
/** * Constructor. */ public function __construct() { $this->namespace = 'rmh/v1'; $this->rest_base = 'posts'; $this->post_type = 'post'; }
From top to bottom:
- rest base is set: yourdomain.com/wp-json/rmh/v1 (or whatever your heart desires!)
- next is the base for the endpoint itself “posts”, not to be confused with the post type. (yourdomain.com/wp-json/rmh/v1/posts)
- last is the post type we are getting. In this example, we are grabbing “post”
Registering your new route!
/** * Register the component routes. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ) ) ); }
From top to bottom:
- We are combing your namespace and base together
- making the request READABLE so to be clear that means this is a get request (see here for me details)
- we will then get then return the code placed in our
get_items
function - it then checks to make sure we have permission to do so through our
get_items_permissions_check
- and it checks what are the possible arguments that can be used with it
get_collection_params()
Retrieving posts and setting Response Headers for pagination (X-WP-Total, X-WP-TotalPages)
/** * Retrieve posts. */ public function get_items( $request ) { $args = array( 'post_type' => $this->post_type, 'posts_per_page' => $request['per_page'], 'paged' => $request['page'], //'name' => $request['slug'], ); // use WP_Query to get the results with pagination $query = new WP_Query( $args ); // if no posts found return if( empty($query->posts) ){ return new WP_Error( 'no_posts', __('No post found'), array( 'status' => 404 ) ); } // set max number of pages and total num of posts $posts = $query->posts; $max_pages = $query->max_num_pages; $total = $query->found_posts; foreach ( $posts as $post ) { $response = $this->prepare_item_for_response( $post, $request ); $data[] = $this->prepare_response_for_collection( $response ); } // set headers and return response $response = new WP_REST_Response($data, 200); $response->header( 'X-WP-Total', $total ); $response->header( 'X-WP-TotalPages', $max_pages ); return $response; } /** * Check if a given request has access to post items. */ public function get_items_permissions_check( $request ) { return true; } /** * Get the query params for collections */ public function get_collection_params() { return array( 'page' => array( 'description' => 'Current page of the collection.', 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', ), 'per_page' => array( 'description' => 'Maximum number of items to be returned in result set.', 'type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint', ), ); } /** * Prepares post data for return as an object. */ public function prepare_item_for_response( $post, $request ) { $data = array( 'name' => $post->post_title, 'copy' => $post->post_content, 'featured_img' => get_the_post_thumbnail_url($post->ID, 'full'), 'external_link' => get_field('external_post_link',$post->ID), 'date' => date( "F j, Y", strtotime($post->post_date)) ); return $data; }
From top to bottom get_items
:
- We are setting are variables for
WP_Query
- why that instead of get_posts? Because we want to return the total number of pages and posts with it as well
- We then set the variables
- If nothing then let us know
- Set the variables for what we will loop through, total number pages and total number of posts found
- Loop through the posts that will check the data we set and return it in the response
- Finish
get_items
up by returning the response and setting the response headers for pagination - We then set the permission check
- For
get_collection_params
we are setting thepage
andper_page
details and defaults - For
prepare_item_for_response
we are setting the data we literally returning per post and is called above inget_items
- You, of course, would set this based on your needs.
- In this example, we are getting the title/content/featured image/an ACF Field/date
If we put it all together it looks like this:
class REFINED_POST_ENDPOINT extends WP_REST_Controller { /** * Constructor. */ public function __construct() { $this->namespace = 'rmh/v1'; $this->rest_base = 'posts'; $this->post_type = 'post'; } /** * Register the component routes. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ) ) ); } /** * Retrieve posts. */ public function get_items( $request ) { $args = array( 'post_type' => $this->post_type, 'posts_per_page' => $request['per_page'], 'paged' => $request['page'], //'name' => $request['slug'], ); // use WP_Query to get the results with pagination $query = new WP_Query( $args ); // if no posts found return if( empty($query->posts) ){ return new WP_Error( 'no_posts', __('No post found'), array( 'status' => 404 ) ); } // set max number of pages and total num of posts $posts = $query->posts; $max_pages = $query->max_num_pages; $total = $query->found_posts; foreach ( $posts as $post ) { $response = $this->prepare_item_for_response( $post, $request ); $data[] = $this->prepare_response_for_collection( $response ); } // set headers and return response $response = new WP_REST_Response($data, 200); $response->header( 'X-WP-Total', $total ); $response->header( 'X-WP-TotalPages', $max_pages ); return $response; } /** * Check if a given request has access to post items. */ public function get_items_permissions_check( $request ) { return true; } /** * Get the query params for collections */ public function get_collection_params() { return array( 'page' => array( 'description' => 'Current page of the collection.', 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', ), 'per_page' => array( 'description' => 'Maximum number of items to be returned in result set.', 'type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint', ), ); } /** * Prepares post data for return as an object. */ public function prepare_item_for_response( $post, $request ) { $data = array( 'name' => $post->post_title, 'copy' => $post->post_content, 'featured_img' => get_the_post_thumbnail_url($post->ID, 'full'), 'external_link' => get_field('external_post_link',$post->ID), 'date' => date( "F j, Y", strtotime($post->post_date)) ); return $data; } } add_action( 'rest_api_init', function () { $controller = new REFINED_POST_ENDPOINT(); $controller->register_routes(); } );
Now you’ll have the following:
yourdomain.com/wp-json/rmh/v1/posts
yourdomain.com/wp-json/rmh/v1/posts?page=2
etcyourdomain.com/wp-json/rmh/v1/posts?per_page=3
etcyourdomain.com/wp-json/rmh/v1/posts?per_page=5&page=3
and so on!