Ensuring cacheable metadata in responsesfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Every example so far has hand-written cache tags and contexts directly inside a render array's #cache key — but what if the thing your render array depends on is an object, not a literal string you can type in? A config entity, a loaded node, a plugin instance — each of these already knows its own cache tags, contexts, and max-age internally. CacheableDependencyInterface is the contract that lets your code ask an object "what are your cacheability requirements?" and merge the answer straight into a render array, instead of duplicating that knowledge by hand.

What you'll learn in this lesson

  • What CacheableDependencyInterface actually requires a class to implement
  • How \Drupal::service('renderer')->addCacheableDependency() merges an object's metadata into your build array
  • Why this matters more as your code composes several dependencies together

The interface itself

interface CacheableDependencyInterface {
  public function getCacheContexts();
  public function getCacheTags();
  public function getCacheMaxAge();
}

Three methods, each returning exactly the kind of value you've already been writing by hand in a #cache array: getCacheContexts() returns an array of context strings (like ['user']), getCacheTags() returns an array of tag strings (like ['node:42']), and getCacheMaxAge() returns a number of seconds (or Cache::PERMANENT). Every content entity in Drupal core already implements this — when you load a node, it can already tell you its own tags without you ever writing 'node:' . $node->id() yourself.

Merging it into a render array

$node = \Drupal::entityTypeManager()->getStorage('node')->load(42);

$build['teaser'] = [
  '#markup' => $node->label(),
];

// This one call pulls $node's own tags, contexts, and max-age into $build,
// merging with whatever cacheability metadata $build already had.
\Drupal::service('renderer')->addCacheableDependency($build['teaser'], $node);

Compare this to typing '#cache' => ['tags' => ['node:42']] by hand: it looks almost equivalent for one node, but addCacheableDependency() is doing something you can't easily replicate manually — it's asking the node object itself, at the moment you call it, what its current cacheability actually is. If a future version of Drupal (or a contrib module) changes what tags a node contributes, your code automatically stays correct, because you never hard-coded the assumption in the first place.

This compounds when you combine several dependencies. Say your render array's output depends on a node and a config entity and the current user's role. Call addCacheableDependency() once per dependency, and the renderer correctly unions all three sets of tags, all three contexts, and takes the minimum of all three max-ages (the whole thing can only be cached as long as its most restrictive dependency allows) — bookkeeping that gets genuinely error-prone to do by hand as the number of dependencies grows.

Quick check: you call addCacheableDependency() with a node that has getCacheMaxAge() returning 3600 (one hour), and separately your render array's own #cache already specified 'max-age' => Cache::PERMANENT. What's the resulting max-age? Reasoning it through: cacheability metadata always combines toward the most restrictive constraint, never the most permissive — so the final max-age is 3600 seconds, not permanent, since permanent caching would be wrong the moment that node's own hour-long limit is reached.

Key takeaways

  • CacheableDependencyInterface requires getCacheContexts(), getCacheTags(), and getCacheMaxAge() — every content entity in Drupal core already implements it.
  • \Drupal::service('renderer')->addCacheableDependency($build, $object) pulls an object's own cacheability metadata into a render array, instead of you re-typing tags/contexts you'd otherwise have to keep in sync by hand.
  • Combining multiple dependencies always moves toward the most restrictive result — the union of all tags and contexts, and the minimum of all max-ages.
  • Prefer this over hand-written #cache arrays whenever the thing you depend on is an object that already knows its own cacheability — it stays correct even if that object's own tags change later.

Coming up next

That wraps up the Cache API section — tags, contexts, manual invalidation, and now dependency-driven metadata. Next, we shift to a different mechanism for injecting dynamic values into text: Drupal's Token system, starting with how to define your own custom tokens.