Adding cache metadata to REST resourcesfor Drupal 11 , 10 , 9 , and 8

Last updated :  

A REST resource with no cache metadata forces every single request through your full Drupal bootstrap and controller logic — even for two identical requests made a second apart. This is the same principle as page and render caching from earlier in this tutorial, applied specifically to API responses: tell Drupal what the response depends on, and it can serve repeat requests without recomputing anything.

What you'll learn in this lesson

  • Why a custom REST resource needs explicit cache metadata — it isn't automatic
  • Attaching cache tags, contexts, and max-age to a REST response
  • Why JSON:API's built-in resources already do this correctly for you

A REST resource with no caching at all

public function get() {
  $data = $this->buildResponseData();
  return new ResourceResponse($data);
  // No cache metadata attached — every request re-runs buildResponseData()
  // in full, regardless of whether anything actually changed.
}

Adding real cache metadata

public function get(NodeInterface $node) {
  $data = $this->buildResponseData($node);
  $response = new ResourceResponse($data);

  $cacheable_metadata = CacheableMetadata::createFromObject($node);
  $cacheable_metadata->addCacheContexts(['url.query_args']);
  $cacheable_metadata->setCacheMaxAge(3600);
  $response->addCacheableDependency($cacheable_metadata);

  return $response;
}

This is exactly the cache tags/contexts/max-age vocabulary from the Caching Strategies section, applied to a ResourceResponse instead of a render array. CacheableMetadata::createFromObject($node) pulls the node's own cache tags automatically — the response gets invalidated correctly when that specific node changes, without you hand-listing tags yourself.

Why this isn't automatic for custom REST resources: Drupal has no way to know what data your buildResponseData() method actually depends on — that's information only your code has. Unlike an entity view builder (which automatically knows it's rendering a specific entity), a hand-written REST resource must explicitly declare its own dependencies, or Drupal correctly assumes the response is uncacheable rather than risk serving stale data.

JSON:API already does this correctly

Core's JSON:API module (unlike a hand-written REST resource) already attaches correct cache metadata to every response automatically, derived from the actual entities and fields included — which is exactly why JSON:API is generally the better default choice over hand-rolled REST resources for standard entity CRUD, and why the rest of this section focuses on optimizing JSON:API specifically rather than teaching you to hand-roll cache metadata for every custom resource.

Quick check: you write a custom REST resource with no explicit cache metadata at all. Is the response automatically uncacheable, or does Drupal guess reasonable defaults? If you said automatically uncacheable — Drupal never guesses at cacheability for data it doesn't understand — you've understood exactly why this lesson exists.

Key takeaways

  • A custom REST resource has no automatic cache metadata — you must attach it explicitly via CacheableMetadata.
  • CacheableMetadata::createFromObject() pulls an entity's own cache tags automatically, saving you from hand-listing them.
  • JSON:API's built-in resources already handle this correctly — a real reason to prefer it over custom REST resources for standard entity access.
  • Uncached REST responses re-run their full logic on every single request, identical or not.

Coming up next

Caching handles repeat requests. Next, we look at reducing the cost of any individual JSON:API request in the first place — trimming what it actually fetches and returns.