0

We have registered a custom REST GET endpoint using register_rest_route(), consisting in the retrieval of posts of a custom post type (my_custom_post). We're using our own endpoint to do so as we do not want to allow direct requests to the endpoint of the concerned post type.

Code Example:

Plugin File Content:

add_action(
    'rest_api_init',
    function () {
        register_rest_route(
            'namespace',
            '/endpoint',
            [
                [
                    'methods'  => 'GET',
                    'callback' => [
                        SampleController::class,
                        'test'
                    ],
                    'args'     => [
                        'test' => [
                            'type'        => 'string',
                            'description' => 'This is just a test',
                            'required'    => true
                        ]
                    ]
                ]
            ]
        );
    }
);

Request Controller:

class SampleController {
    
    public static function test( WP_REST_Request $request ):WP_REST_Response
    {
    
        $search_request = new WP_REST_Request(
            method: 'GET',
            route:  '/wp/v2/my_custom_post'
        );
        
        $search_request->set_param(
            '_fields',
            'id,meta.my_custom_meta'
        );
        
        $search_response = rest_do_request($search_request);
    
        $search_response_data = rest_get_server()->response_to_data(
            response: $search_response,
            embed:    false
        );
    
        $answer = new WP_REST_Response(
            [
                'success' => true,
                'message' => $search_response_data
            ],
            200
        );

        $answer->header(
          key: 'Cache-Control',
          value: 'no-cache',
          replace: true
        );

        return $answer;
        
    }
    
}

JavaScript:

async function sendRequest() {
    
    const response = await fetch(
        'https://example.org/namespace/endpoint?test=hello',
        {
            method : 'GET',
            headers: {
                'Content-Type':'application/json',

            },
            mode   : 'same-origin',
            cache  : 'no-cache'
        }
    );
    
    const responseData = await response.json();

    if (!response.ok || response.status !== 200) {
        throw Error('An Error ocurred');
    }
    
    return responseData;
    
}

The requests to the endpoint do not report any errors ever, and everything works fine.

The problem is that, let's say you have 6 posts of the type my_custom_post. Via the code above, you fire the request once, and get your data. You then go to the WP Admin, update exclusively the my_custom_meta of 2 of your posts, and then fire the request again. Problem: the changes are not immediately reflected in your returned response; it always takes about 10 minutes and / or multiple requests. And this although we're applying the Cache-Control: no-cache headers to our request as well as to the response.

So we wonder, does wordpress actually cache responses of internally executed REST API requests on the server-side? Or what is happening here?

We've also tried to apply the Cache-Control headers to our internal request, by changing the body of the Controller's test method above to:

...
$search_request->set_param(
    '_fields',
    'id,meta.my_custom_meta'
);

$search_request->set_headers(
    headers:  [ 'Cache-Control' => 'no-cache' ],
    override: false
);

$search_response = rest_do_request($search_request);

$search_response->header(
    key:     'Cache-Control',
    value:   'no-cache',
    replace: true
);
...

But the problem persists.

We've then also heard about the rest_send_nocache_headers hook thanks to this ticket; but we're unsure about how applying that filter to a specific REST API namespace only (because we basically do not want any of our endpoints of a specific namespace to return cached responses).

5
  • there's no code to debug in your question so it's difficult to tell what's going on, it could be that you're using a cached function inside your rest route, or that there's a mistake so simple you're blind to it, please edit your question to include the smallest version of your code that reproduces the issue, including how you are testing this and came to the conclusion it was caching somewhere
    – Tom J Nowell
    Commented Apr 17, 2023 at 20:25
  • Everything needed is in the question, no code would provide more detail which is why I omitted additional code. I'm digging at the moment and it indeed seems that the WP REST API caches responses by default.
    – DevelJoe
    Commented Apr 17, 2023 at 20:36
  • that's a gross misinterpretation of that ticket, WordPress does not cache it, rather the ticket is about sending HTTP headers telling other services not to cache it, e.g. CDNs such as cloudflare that sit in front of WordPress. Also, you might think you've included everything, but I disagree. It would be better to include the information in order to dispel any doubt and avoid unnecessary clarifying questions. Otherwise the only canonical answer I could write for you right now is that no, WP does not cache REST API responses, but that's not going to be very helpful for you.
    – Tom J Nowell
    Commented Apr 17, 2023 at 21:17
  • 1
    code example added
    – DevelJoe
    Commented Apr 17, 2023 at 22:14
  • I'd switch from no-cache to no-store based on a reading of hacks.mozilla.org/2016/03/…, in fetch no-cache doesn't mean never cache if a copy already exists in the HTTP cache, the semantics aren't quite as expected
    – Tom J Nowell
    Commented Apr 20, 2023 at 14:32

1 Answer 1

2

Does WP REST API cache internally executed requests?

No it does not, not out of the box. CDNs and caching plugins can and do interfere however, especially for unauthenticated REST requests.

As an example to demonstrate this with hard results and test my own conclusion I added a random number generator to my site:

https://tomjn.com/wp-json/tomjn/v1/random

This is the code:

function tomjn_rest_random() {
        return rand(1,100);
}       

add_action( 'rest_api_init', function () {
    register_rest_route( 'tomjn/v1', '/random/', [
        'methods' => 'GET',
        'permission_callback' => '__return_true',
        'callback' => 'tomjn_rest_random',
        'show_in_index' => false,
    ] );
} );

This includes rest_do_request, WordPress has no page/response level caching built in, these are all provided by 3rd party services/software.

Note though that WP does cache some functions if a dedicated object cache is present, but never anything as large as an entire REST API response. This is on the scale of individual posts/terms/meta fetched from the database, or expensive calls such as figuring out post counts for a category, smaller atomic pieces of data that aren't enough to explain your results.

A Note on HTTP request/response cache headers

no-cache does not do what you think it does! It means cache the result, but mandates that a request must be made to validate that it's still current. So unless you've implemented etags etc the browser might not realise the response has changed, especially since this isn't a static file.

From MDN:

The no-cache response directive indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse, even when the cache is disconnected from the origin server.

If you want caches to always check for content updates while reusing stored content, no-cache is the directive to use. It does this by requiring caches to revalidate each request with the origin server.

Note that no-cache does not mean "don't cache". no-cache allows caches to store a response but requires them to revalidate it before reuse. If the sense of "don't cache" that you want is actually "don't store", then no-store is the directive to use.

Instead consider using no-store:

The no-store response directive indicates that any caches of any kind (private or shared) should not store this response.

Further reading:

5
  • EDIT: We've just discovered now that there was a deployment error, and that the lines $answer->header( key: 'Cache-Control', value: 'no-cache', replace: true ); have not been deployed to our code (to enforce cache-revalidation not only on the client-side, but also on the server-side). With this, it's all fully working now, so it seems to be manadatory to enforce cache revalidation not only on the client, but also on the server-side, makes total sense actually.
    – DevelJoe
    Commented Apr 21, 2023 at 0:11
  • Notes: MDN mentions this on no-store vs no-cache, which is why we favored no-cache, following their recommendation. We've been using no-store previously; but MDN also mentions that the major browsers do not support requests with no-store. Try a fetch request with no-store in Firefox, and you'll see that the HTTP Request header sent will be no-cache automatically, instead.
    – DevelJoe
    Commented Apr 21, 2023 at 0:24
  • And if I may add a thought; in our use-case vs your example, there's an additional REST API request executed via rest_do_request, hence an additional chance for a cache bust / revalidation may being necessary for that. There are some questions in this forum mentioning the issue of unwanted cached responses to the WP Post API. It theoretically makes sense that API requests involving DB queries (as queries for posts) involve a different way of caching vs your example; which is a simple script. Maybe that explains why the Cache-Control stuff was required in our case, but not in your example.
    – DevelJoe
    Commented Apr 21, 2023 at 0:35
  • No, no there is not. WordPress does not cache REST API responses. Your nested call does not add an opportunity to bust WP cache because there is none to bust. HTTP cache control headers don’t influence WP behaviour they influence external behaviour. It’s far more likely that you have a CDN or caching layer in front of WordPress such as cloudflare, or that you’ve directed the browser to perform browser caching. The complexity of the REST API endpoint is irrelevant here, unless you yourself added caching WP does not cache API responses. It doesn’t even have the code to do so
    – Tom J Nowell
    Commented Apr 21, 2023 at 7:57
  • Also note that while my example was simple, I’ve used far more complex endpoints and never had to worry about this before in vanilla WP. If you’re on a managed WP host there’s a very high chance your host is caching unauthenticated REST responses, and that my example has the same problem on your server. Again WP does not cache API responses and does not have that ability. The cache problem is elsewhere
    – Tom J Nowell
    Commented Apr 21, 2023 at 7:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.