Skip to main content
Categories
General

Optimizing WordPress REST API for Better Performance

  • Post Date
    Thu Jun 19 2025

The WordPress REST API offers incredible flexibility for headless setups, mobile apps, and custom admin interfaces. However, without proper optimization, API requests can become sluggish, especially as your site grows.

In this post, I’ll share techniques to reduce response time and payload size for WordPress REST API endpoints based on real-world implementations.

Understanding REST API Performance Bottlenecks

Before diving into optimizations, let’s identify common REST API performance issues:

  1. Excessive data retrieval – Fetching more fields than needed
  2. Unnecessary nested data – Requesting embedded resources that aren’t used
  3. Inefficient queries – Poor database queries behind the scenes
  4. Authentication overhead – Complex authentication checking on every request
  5. Missing caching layers – Not utilizing available caching mechanisms

1. Limit Response Fields

One of the simplest optimizations is to request only the fields you need using the _fields parameter:

/wp-json/wp/v2/posts?_fields=id,title,excerpt

Before: ~15KB per post with 20+ fields After: ~2KB per post with just 3 fields

This significantly reduces JSON parsing time and network transfer.

For custom endpoints, you can implement field filtering:

add_filter('rest_prepare_post','my_rest_prepare_post',10,3);/** * Filter the post data given in the REST API. * * This filter is required to only expose the fields that are requested via the `_fields` parameter in the URL. * * @paramWP_REST_Response $response The response object. * @paramWP_Post          $post     The post object. * @paramWP_REST_Request  $request  The request object. * * @returnWP_REST_Response The filtered response object. */functionmy_rest_prepare_post($response,$post,$request){$fields=$request->get_param('_fields');if(!$fields){return$response;}$fields=explode(',',$fields);$data=$response->get_data();$filtered_data=array();foreach($fieldsas$field){if(isset($data[$field])){$filtered_data[$field]=$data[$field];}}$response->set_data($filtered_data);return$response;}

2. Optimize _embed Requests

The _embed parameter fetches related resources, but can be expensive. Instead of using the full embed, specify only what you need:

/wp-json/wp/v2/posts?_embed=author,wp:featuredmedia

If you’re building custom endpoints, control embedded data carefully:

register_rest_field('post','author_info',['get_callback'=>function($post){$author= get_user_by('id',$post['author']);return['name'=>$author->display_name,'avatar'=> get_avatar_url($author->ID,['size'=>96])];}]);

This custom field is much more efficient than embedding the full author object.

3. Implement Custom Caching

WordPress doesn’t cache REST API responses by default. Add a custom caching layer:

add_filter('rest_post_dispatch','cache_rest_api_response',10,3);add_filter('rest_pre_dispatch','get_cached_rest_api_response',10,3);/** * Cache REST API responses. * * This function caches the REST API response for a given route and query string. * The cache is stored in a transient with a key that is an MD5 hash of the route and query string. * The cache expiration is set to 5 minutes by default, but can be overridden for specific endpoints. * For example, the `/wp/v2/posts` endpoint is cached for 1 hour. * * @parammixed  $response The REST API response. * @paramobject $handler   The REST API handler. * @paramobject $request   The REST API request. * * @returnmixed The filtered response. */functioncache_rest_api_response(mixed$response,object$handler,object$request):mixed{if(!is_wp_error($response)){$cache_key='rest_'.md5($request->get_route().'?'.$_SERVER['QUERY_STRING']);// Set cache expiration based on endpoint.$cache_time=300;// 5 minutes default.if(str_contains($request->get_route(),'/wp/v2/posts')){$cache_time=3600;// 1 hour for posts.}set_transient($cache_key,$response,$cache_time);}return$response;}/** * Retrieves a cached REST API response. * * This function is a filter on the `rest_pre_dispatch` hook. * It checks if a transient exists for the given request route and query string. * If a transient exists, it is returned instead of the actual response. * * @parammixed  $response The original response. * @paramobject $handler   The REST API handler. * @paramobject $request   The REST API request. * * @returnmixed The cached response if it exists, otherwise the original response. */functionget_cached_rest_api_response(mixed$response,object$handler,object$request):mixed{$cache_key='rest_'.md5($request->get_route().'?'.$_SERVER['QUERY_STRING']);$cached_response=get_transient($cache_key);if($cached_response){return$cached_response;}return$response;}

4. Use Collection Pagination Efficiently

Large collections can cause significant slowdowns. Always use pagination:

/wp-json/wp/v2/posts?per_page=10&page=2

For client applications, implement infinite scroll or “load more” instead of requesting all items at once.

You can also optimize the pagination headers with:

add_filter('rest_post_dispatch','optimize_collection_count',10,3);/** * Optimize collection count for large datasets. * * If we have more than 1000 posts, use an estimate for the total count. * * @param\WP_REST_Response $response The response object. * @param\WP_REST_Server   $handler  The response handler. * @param\WP_REST_Request  $request  The request object. * * @return\WP_REST_Response The modified response. */functionoptimize_collection_count(\WP_REST_Response$response,\WP_REST_Server$handler,\WP_REST_Request$request):\WP_REST_Response{// Only run on collection endpoints.if(!empty($response->get_matched_route())&&str_contains($response->get_matched_route(),'/wp/v2/posts')){// Get the total count from the headers$total=$response->get_headers()['X-WP-Total'];// If we have many posts, use an estimate.if($total>1000){$response->header('X-WP-Total','1000+');$response->header('X-WP-TotalPages',ceil(1000/$request['per_page']));}}return$response;}

This prevents expensive COUNT(*) queries when you have many posts.

5. Optimize Custom Endpoint Queries

For custom endpoints, ensure your database queries are optimized:

register_rest_route('my-api/v1','/popular-posts',['methods'=>'GET','callback'=>'get_popular_posts','permission_callback'=>'__return_true']);/** * Retrieves an array of popular posts ordered by view count. * * @returnarray */functionget_popular_posts():array{global$wpdb;// Use a more efficient query than WP_Query for specific cases.$posts=$wpdb->get_results($wpdb->prepare("SELECT p.ID, p.post_title, pm.meta_value as view_count FROM{$wpdb->posts} pJOIN{$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_status = 'publish' AND p.post_type = 'post'AND pm.meta_key = 'view_count'ORDER BY pm.meta_value+0DESCLIMIT %d",10));returnarray_map(function($post){return['id'=>$post->ID,'title'=>$post->post_title,'views'=>$post->view_count,'link'=> get_permalink($post->ID)];},$posts);}

This direct query is much faster than using WP_Query for this specific use case.

6. Implement Server-Side Request Caching

Add object caching to prevent duplicate database queries:

/** * Retrieve posts from the database with caching. * * This function wraps the built-in `WP_Query` class with caching to improve performance. * It takes a `WP_REST_Request` object as a parameter and returns an array of associative * arrays containing the post ID, title, and other fields. * * The cache key is generated by serializing the request parameters and taking the MD5 hash. * The cache is stored for 5 minutes. * * @param\WP_REST_Request $request The request object. * * @returnarray An array of associative arrays containing the post data. */functionget_posts_with_cache(\WP_REST_Request$request):array{$cache_key='api_posts_'.md5( serialize($request->get_params()));$posts=wp_cache_get($cache_key,'api');if(false===$posts){$query=new\WP_Query(['post_type'=>'post','posts_per_page'=>$request['per_page']??10,'paged'=>$request['page']??1,// Other parameters...]);$posts=array_map(function($post){return['id'=>$post->ID,'title'=>$post->post_title,// Other fields...];},$query->posts);wp_cache_set($cache_key,$posts,'api',300);// Cache for 5 minutes}return$posts;}

7. Use External Caching and CDNs

For high-traffic sites, implement external caching:

add_filter('rest_post_dispatch','add_cache_control_headers');/** * Adds Cache-Control header to API responses. * * This filter adds a Cache-Control header with a max-age of 60 seconds to API responses. * This allows the browser to cache API requests for 1 minute, improving performance. * * @param\WP_HTTP_Response|\WP_Error $response The response object or error. * * @return\WP_HTTP_Response|\WP_Error The modified response object. */functionadd_cache_control_headers(\WP_HTTP_Response|\WP_Error$response):\WP_HTTP_Response|\WP_Error{if(!is_wp_error($response)){$response->header('Cache-Control','public, max-age=60');// 1 minute}return$response;}

This helps CDNs and browsers cache your API responses.

8. Optimize Authentication

REST API authentication can add overhead. If you’re building a public-facing API, consider:

add_filter('rest_authentication_errors','optimize_api_auth',5,3);/** * Only run authentication when needed * * This function is used as a filter for `rest_authentication_errors`. It allows * the API to skip authentication for GET requests to specific endpoints. * * @parammixed  $result   The value to return instead of the HTTP request. * @paramobject $server   The server object. * @paramobject $request  The request object. * * @returnmixed The filtered value. */functionoptimize_api_auth(mixed$result,object$server,object$request):mixed{// Skip authentication for GET requests to specific endpointsif($request->get_method()==='GET'&&str_contains($request->get_route(),'/wp/v2/posts')){// Return true to indicate that authentication should be skipped.returntrue;}return$result;}

9. Benchmark and Compare

Before and after implementing these optimizations, benchmark your API:

# Before optimizationtime curl -s https://yourdomain.com/wp-json/wp/v2/posts# After optimizationtime curl -s https://yourdomain.com/wp-json/wp/v2/posts?_fields=id,title,excerpt

Conclusion

The WordPress REST API is powerful but needs optimization for production use. By implementing these techniques, you can significantly improve performance, reduce server load, and create a better experience for your users.