# Cache Public REST API Responses for Visitors

> Stores the responses of public GET requests to the REST API (such as post lists used by headless front ends and block widgets) for a few minutes for logged-out visitors, and clears the cache when content changes.

- Type: PHP
- Runs: Run everywhere
- Category: [Performance](https://wpide.com/library/category/performance/)
- Tags: rest-api, cache, headless
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/perf-cache-public-rest-responses/

## Code

```php
// Route prefixes to cache, and for how long (seconds).
$wpide_rest_cache_routes = array( '/wp/v2/posts', '/wp/v2/pages', '/wp/v2/categories', '/wp/v2/tags', '/wp/v2/media' );
$wpide_rest_cache_ttl    = 5 * MINUTE_IN_SECONDS;

$wpide_rest_cache_key = function ( WP_REST_Request $request ) use ( $wpide_rest_cache_routes ) {
    if ( 'GET' !== $request->get_method() || is_user_logged_in() || isset( $request['_embed'] ) ) {
        return '';
    }

    foreach ( $wpide_rest_cache_routes as $prefix ) {
        if ( 0 === strpos( $request->get_route(), $prefix ) ) {
            $params = $request->get_query_params();
            ksort( $params );
            return 'wpide_rest_' . (int) get_option( 'wpide_rest_cache_gen', 1 ) . '_' . md5( $request->get_route() . wp_json_encode( $params ) );
        }
    }

    return '';
};

add_filter( 'rest_pre_dispatch', function ( $result, $server, $request ) use ( $wpide_rest_cache_key ) {
    $key = $wpide_rest_cache_key( $request );

    if ( null !== $result || ! $key ) {
        return $result;
    }

    $cached = get_transient( $key );

    if ( is_array( $cached ) ) {
        $response = new WP_REST_Response( $cached['data'], $cached['status'] );
        $response->set_headers( array_merge( $cached['headers'], array( 'X-WPIDE-Cache' => 'HIT' ) ) );
        return $response;
    }

    return $result;
}, 10, 3 );

add_filter( 'rest_request_after_callbacks', function ( $response, $handler, $request ) use ( $wpide_rest_cache_key, $wpide_rest_cache_ttl ) {
    $key = $wpide_rest_cache_key( $request );

    if ( $key && $response instanceof WP_REST_Response && 200 === $response->get_status() ) {
        $data = rest_get_server()->response_to_data( $response, false );
        set_transient( $key, array( 'data' => $data, 'status' => 200, 'headers' => $response->get_headers() ), $wpide_rest_cache_ttl );
    }

    return $response;
}, 99, 3 );

$wpide_rest_cache_flush = function () {
    update_option( 'wpide_rest_cache_gen', (int) get_option( 'wpide_rest_cache_gen', 1 ) + 1, false );
};
add_action( 'save_post', $wpide_rest_cache_flush );
add_action( 'deleted_post', $wpide_rest_cache_flush );
add_action( 'edited_term', $wpide_rest_cache_flush );
add_action( 'delete_attachment', $wpide_rest_cache_flush );
```

## Notes

Only routes in $wpide_rest_cache_routes are cached and requests using _embed are skipped; with a persistent object cache the entries live in memory instead of the database.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Cache Public REST API Responses for Visitors" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
