Picture a whiteboard in a shared office with a sticky note on it that says "Q3 sales numbers." Anyone can glance at the board and instantly know what it's about — and the moment Q3 sales numbers actually change, whoever updates them knows exactly which sticky note to tear off and rewrite. A Drupal cache tag is that sticky note: a plain string label attached to a piece of cached render output, saying "this depends on X" — so that when X changes, Drupal knows precisely which cached copies to throw away, without touching anything else.
What you'll learn in this lesson
- What a cache tag actually is, and Drupal core's own naming convention for them
- How to attach a tag to a render array with the
#cacheproperty - How
Cache::invalidateTags()uses tags to clear exactly the right cached items, across every cache bin, in one call - Why tagging correctly matters more than caching aggressively
The problem cache tags solve
Say you render a "Recent articles by this author" block and cache it, because building it means querying and rendering several nodes — genuinely expensive work you don't want to repeat on every page view. Now the author edits one of those articles. Somewhere, a cached copy of that block is now stale. Without tags, your only real option is caching for a very short time (defeating the point) or clearing far more than necessary. With tags, you attach node:42 (or whatever that article's node ID is) to the block when you cache it — and the moment node 42 is saved, Drupal's own save logic calls Cache::invalidateTags(['node:42']), which finds and clears every cached item anywhere that was tagged with it. Nothing else is touched.
Attaching a tag to a render array
$build['recent_articles'] = [
'#theme' => 'item_list',
'#items' => $article_titles,
'#cache' => [
'tags' => ['node:42', 'node_list'],
],
];
The #cache property is how any render array declares its own cacheability metadata — tags are just one part of it (you'll meet contexts and max-age, its two siblings, in the next lesson). tags takes a plain array of strings. Here, node:42 says "invalidate me if node 42 changes," and node_list — a generic tag Drupal core itself invalidates whenever any node is saved or deleted — says "invalidate me if the overall set of nodes changes at all," which matters if a *new* article by this author could also affect what "recent" means.
Drupal core's own naming convention
You don't have to invent a tag scheme from scratch — Drupal core already tags every entity as {entity_type_id}:{id} (node:42, taxonomy_term:7, user:3), and every entity's own toUrl()/render output already carries its own tag automatically. When you build something that depends on an entity without being that entity's own render output — like our recent-articles block — you attach that same tag yourself, by hand, exactly as shown above.
node:42 too, without you doing anything extra. This is why a single node edit can correctly invalidate a cached page, a cached block, and a cached view all at once — they were all bubbling the same tag upward.Invalidating by tag
use Drupal\Core\Cache\Cache;
Cache::invalidateTags(['node:42']);
Cache::invalidateTags() is a static method that reaches into every registered cache bin on the site at once — the render cache, the page cache, dynamic page cache, and any custom bin — and marks every entry carrying that tag as invalid. This is exactly what Drupal core calls, automatically, every time you save a node, taxonomy term, or any other content entity — which is why cached pages usually "just work" correctly without you writing a single line of invalidation code yourself, as long as you tagged things correctly in the first place.
Quick check: you cache a "top 5 comments" block and tag it only with
comment_list. A user then edits the wording of one existing comment (not adding or removing any). Will the cached block update? Reasoning it through:comment_listis invalidated when a comment is added or deleted, not necessarily on every edit — so a wording edit on an existing comment might not invalidate it unless you also tagged the block with that specificcomment:{id}. This is exactly the kind of gap that tagging too broadly (or too narrowly) creates — always tag for what actually changed, not just what you assume changes.
Key takeaways
- A cache tag is a plain string describing a dependency; Drupal core's own convention is
{entity_type_id}:{id}(e.g.node:42). - Attach tags via
'#cache' => ['tags' => [...]]on any render array whose output depends on something that can change. - Tags bubble automatically up a render tree — a child element's tags become the parent's tags too, all the way to the page cache.
Cache::invalidateTags()is a static, cross-bin operation — the same mechanism Drupal core uses internally every time an entity is saved.- Generic list tags (
node_list,comment_list, etc.) cover "the set changed"; specific entity tags cover "this one thing changed" — use both when a listing depends on both.
Coming up next
Tags answer "when should this be thrown away?" The next lesson tackles a related but different question — "how many different cached copies of this do I even need?" — through cache contexts, using the exact 'user' context Drupal core uses to keep one visitor's cached content from leaking into another's.