WordPress’s own REST API endpoints get cached for roughly 1 minute by default — but custom endpoints a plugin or theme adds don’t inherit that behavior automatically, and are frequently resource-intensive enough to genuinely strain a site under real traffic if left completely uncached. One documented real-world example showed a product-listing custom endpoint dropping from roughly 1.5 seconds to about 45 milliseconds once properly cached in Redis — a difference large enough to be the actual bottleneck on a site that otherwise looks well-optimized.
Why Custom Endpoints Specifically Need Deliberate Caching
A custom REST endpoint commonly runs its own database queries, potentially joining or aggregating data in ways core’s standard post/page endpoints don’t — and unlike a cacheable static page, that work happens fresh on every single request unless something explicitly stores and reuses the result. The WordPress Transients API is the standard mechanism: store an expensive query’s result under a transient key, check for a valid cached transient before re-running the expensive logic, and only recompute when the cache has actually expired or been invalidated.
The Authentication Trap That Breaks Caching
A per-user security nonce embedded directly in a page or response specifically defeats full-page caching, since the cached version would serve one user’s nonce to every subsequent visitor — a real functional bug, not just a missed optimization. The standard fix: serve the page itself from cache, then fetch the nonce separately via a small AJAX call after the page loads, keeping the cacheable and the per-user pieces cleanly separated rather than mixed into one response.
What Actually Works
| Technique | What it solves |
|---|---|
| Transients API / object cache (Redis) | Stores expensive computed results so repeat requests skip the heavy database work entirely |
Cache-Control HTTP headers on public GET responses |
Lets a CDN or browser cache the response directly, without even reaching your server for repeat requests |
| Keeping per-user data (nonces, personalized content) out of cached responses | Prevents the authentication trap above — cache the shared content, fetch personalized pieces separately |
Deliberate cache invalidation on relevant hooks (e.g. save_post) |
Ensures cached custom-endpoint data doesn’t go stale and serve outdated content after a real update |
What to Actually Do
- Audit any custom REST endpoints your site runs — a theme or plugin-added endpoint, not WordPress core’s default routes — for whether they’re currently cached at all.
- Use the Transients API or your object cache (Redis/Memcached, covered separately) for expensive computed results, rather than running the same expensive query on every request.
- Set explicit
Cache-Controlheaders on public, non-personalized GET endpoints so a CDN can serve repeat requests without hitting your origin server at all. - Keep authenticated/per-user logic separate from cached content specifically, using the AJAX-nonce pattern or equivalent, rather than embedding per-user data directly in a cacheable response.
- Plan cache invalidation from the start, tied to the actual content-change hooks relevant to that endpoint’s data, not as an afterthought once stale data becomes a visible problem.
This pairs with WordPress Object Caching: Redis vs Memcached in 2026 and WordPress REST API Rate Limiting. Want your site’s custom REST endpoints profiled and cached properly? Get in touch.
Frequently Asked Questions
Does WordPress core’s REST API need the same caching attention as custom endpoints?
Less urgently — core’s default endpoints already get a baseline 1-minute cache, while custom endpoints added by a theme or plugin have no such default and need deliberate caching to be added explicitly.
Can I cache an authenticated REST endpoint?
Not directly as a single cached response shared across users — authenticated, per-user requests don’t benefit from standard page caching; the practical fix is separating cacheable shared content from the per-user pieces, as described above.
Is Redis required, or can Transients alone handle this?
Transients work without Redis (they fall back to the options table), but Redis-backed object caching is considerably faster for this use case — Transients are the API you code against, while Redis is what makes storing and retrieving them fast at scale.
Featured image: original illustration.
