Invalidating cache tags or binsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Tags and contexts (the last two lessons) let Drupal invalidate cached content automatically, the moment something it depends on changes. But sometimes you need to reach in and clear something yourself — a "Rebuild cache now" button in an admin form, a migration script that just bulk-updated data no single save operation touched, or a one-off fix during development. This lesson covers doing that deliberately, from your own PHP code.

What you'll learn in this lesson

  • The precise tool — Cache::invalidateTags() — versus the blunt one — deleting an entire bin
  • How to get a handle on a specific cache bin and call delete()/deleteAll() on it
  • Why reaching for the broadest option "just to be safe" is usually the wrong instinct

The real example

Drupal's own cache_example module ships a form with a button that does exactly this, tagging a cached value and then clearing it by tag on demand:

use Drupal\Core\Cache\Cache;

// ... elsewhere, a value was cached with a tag:
$this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT, ['cache_example']);

// ... and later, a button's submit handler clears everything with that tag:
$tags = ['cache_example'];
Cache::invalidateTags($tags);
$this->messenger()->addMessage($this->t('Cache entries with the tag "cache_example" set to 1 in the "cache" bin were invalidated with \Drupal\Core\Cache\Cache::invalidateTags($tags).'));

This is the same static, cross-bin Cache::invalidateTags() method from the tags lesson — the only difference here is that you're calling it directly, on your own schedule, rather than relying on Drupal core to call it automatically after an entity save. It's the precision tool: it clears exactly the entries carrying that tag, wherever they live, and leaves everything else on the site untouched.

The blunter options, and when they're actually appropriate

// Get a handle on a specific cache bin.
$cache_backend = \Drupal::cache(); // the default bin
// or: \Drupal::service('cache.render'); for the render cache specifically

// Delete one known item by its cache ID.
$cache_backend->delete('my_module_specific_cid');

// Wipe an entire bin — every item in it, unconditionally.
$cache_backend->deleteAll();

delete($cid) removes exactly one item you already know the cache ID of — useful when you're managing that one value directly rather than through tags. deleteAll() is the blunt instrument: it wipes an entire bin, no matter what's in it, meaning every other cached value in that bin — not just the one you actually meant to clear — has to be rebuilt from scratch on the next request. On a busy production site, that's a real, visible performance cost, not a free "just in case" reset.

Common beginner mistake: reaching for deleteAll() or drush cache:rebuild out of caution "just to be safe" after almost any change. It works, but at the cost of every visitor paying for a cold cache all at once. If you tagged your cached content correctly in the first two lessons, a precise Cache::invalidateTags(['the-specific-tag']) call almost always does the actual job with far less collateral damage — reach for the bin-wide wipe only when you genuinely mean the whole bin, like during active local development.

Quick check: your module just bulk-imported 500 new taxonomy terms via a script, bypassing each term's own save() method for speed. Nothing was individually saved, so Drupal's automatic tag invalidation never fired. What's the correct, narrow fix — and what's the tempting-but-wrong one? The correct fix is Cache::invalidateTags(['taxonomy_term_list']) (the generic list tag Drupal already uses for "the set of terms changed"), since that's precisely what changed. The tempting-but-wrong one is \Drupal::cache()->deleteAll() or a full drush cr — it "works" but clears unrelated caches across the entire site for no reason.

Key takeaways

  • Cache::invalidateTags(['tag']) is the precise, cross-bin tool for "clear everything that depends on X" — reach for it first.
  • \Drupal::cache('bin_name')->delete($cid) clears one known item by its exact cache ID.
  • deleteAll() wipes an entire bin unconditionally — appropriate for local development resets, not routine production maintenance.
  • Bulk operations that bypass an entity's own save() (direct DB writes, migrations) won't trigger automatic tag invalidation — you have to invalidate the relevant list tag yourself.

Coming up next

So far every example has assumed the render array itself declares its own cache metadata inline. The next lesson covers what happens when that metadata actually lives on an object elsewhere — a config entity, a plugin — via CacheableDependencyInterface, and how to pull it into your render array correctly.