2019 29 Jul

How does entity cache work in Drupal 8

The Drupal Cache API is used to store data that takes a long time to compute. Caching can either be permanent or valid only for a certain time span, and the cache can contain any type of data. To make websites faster Drupal stores web pages in a cache.

Drupal Cache has three properties

  • Cache context creates context variations when render arrays are being generated. If we have user as a context, every user may not have the same user permissions or language.
  • Cache tags define what object the cache depends on. For dependencies on data managed by Drupal, like entities and configuration.
  • Cache max-age is the maximum time that the cache is stored.

Here is an example from a custom block in Drupal 8:

use Drupal\Core\Cache\Cache;

return [
      '#theme' => 'user_profile_template',
      '#user_data' => $user_data,
      '#cache' => [
        'tags' => ['languages', 'timezone'],
        'contexts' => ['node:5', 'user:3'],
        'max-age' => Cache::PERMANENT,

Use of cache tags in Entity Caching

The cached data in different bins becomes old and obsolete at some point of time and requires removal from these bins to accommodate the latest changes. Before Drupal 8, there was no way to identify individual pieces of expired data stored in different cache bins. 

Cache tags provides a way to track which cache items depend on some data managed by Drupal.
If a renderable output which is output of a Controller or a custom block depends on content provided by some entity, we use cache tags to invalidate the data.
For example, if a node is updated, which appear in two views and three blocks. Without cache tags we wouldn't know which cache item to invalidate

The syntax for setting cache tags is thing:identifier. It has to be unique string and cannot contain spaces.

Entities gets caches in the form of <entity type ID>:<entity ID>

'tags' => ['node_list'], //invalidate when any node updates
'tags' => ['node:1','term:2'], //invalidate when node id 1 or term id 2 is updated

We can also define our own cache tag:

  • Request a cache object through \Drupal::cache().
  • Define a Cache ID (cid) value for your data. A cid is a string, which must contain enough information to uniquely identify the data.
  • Call the get() method to attempt a cache read, to see if the cache already contains your data.
  • If your data is not already in the cache, compute it and add it to the cache using the set() method.
$nid = 9;
$cid = 'my_module:' . $nid;

// Check if the cache already contain data.
if ($item = \Drupal::cache()->get($cid)) {
  return $item->data;

// The data to be cached.
$node = Node::load($nid);
$data = [
  'title' => sprintf('## %s', $node->get('title')->getValue()),

// Set the cache for 'my_module:' . $nid cache tag until $node changes.
\Drupal::cache()->set($cid, $data, Cache::PERMANENT, $node->getCacheTags());

A cache item can have multiple cache tags (an array of cache tags), and each cache tag is a string. Drupal associated cache tags with entity and entity listings. It is important to invalidate listings-based caches when an entity no longer exists or when a new entity is created. This can be done using EntityTypeInterface::getListCacheTags(), it enables code listing entities of this type to ensure that newly created entities show up immediately or invalidate the ones that don’t exist.

Cache::invalidateTags is used to invalidate all cached data of a certain cache tag.

// Invalidate all cache items with certain tags.
\Drupal\Core\Cache\Cache::invalidateTags(array('node:1',  'user:7'));

Explaining with an Example

Create a file custom_plugin/ in your custom module.

    class: Drupal\Core\Cache\CacheBackendInterface
      - { name: cache.bin }
    factory: cache_factory:get
    arguments: [my_cache]

This is to declare our cache whose identifier will be my_cache.

Create a routing for your controller custom_plugin/custom_plugin.routing.yml

  path: '/my-cache'
    _controller: 'Drupal\custom_plugin\Controller\CacheController::content'
    _title: 'Cache'
    _permission: 'access content'

Then in our Controller custom_plugin/src/Controller/CacheController.php we will create a custom cache tag to store a dynamic cache item with conditions to invalidate when the value is updated.


namespace Drupal\custom_plugin\Controller;

use Drupal\user\Entity\User;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\Cache;

 * Class CacheController.
class CacheController extends ControllerBase {

   * The cache backend service.
   * @var \Drupal\Core\Cache\CacheBackendInterface
  protected $cacheBackend;

   * Constructs a new CacheController object.
  public function __construct(CacheBackendInterface $cache_backend) {
    $this->cacheBackend = $cache_backend;

   * {@inheritdoc}
  public static function create(ContainerInterface $container) {
    return new static(

   * Build the user dynamic data.
   * @return array
   *   Return the render array of the user dynamic data.
  public function content() {
    $user = User::load(\Drupal::currentUser()->id());

    // Create a custom cache tag.
    $cid = 'custom_plugin:' . $user->id();
    // Check if there is any cache item associated with this cache tag.
    $data_cached = $this->cacheBackend->get($cid);

    if (!$data_cached) {
      // Build the user dynamic data.
      $data = $user->getAccountName() . ' last accessed at ' . date('H:i', $user->getLastAccessedTime());

      // Merge the entity cache of an user entity with our custom tag.
      $tags = Cache::mergeTags(['user:' . $user->id()], [$cid]);

      // // Store the data into the cache.
      $this->cacheBackend->set($cid, $data, CacheBackendInterface::CACHE_PERMANENT, $tags);
    else {
      $data = $data_cached->data;
      $tags = $data_cached->tags;

    // Return a renderable output.
    $build = [
      '#theme' => 'user_data',
      '#user' => $user->id(),
      '#data' => $data,
      '#cache' => [
        'tags' => $tags,
        'context' => ['user'],

    return $build;


Initialize the variables to be used in the template in custom_plugin/custom_plugin.module file.

 * Implements hook_theme().
function custom_plugin_theme() {
  return [
    'user_data' => [
      'variables' => [
        'user' => [],
        'data' => [],

Create a template custom_plugin/templates/user-data.html.twig to print the cache items

<div class="custom-plugin-block">
  <p>User ID: {{ user }}</p>
  <p>{{ data }}</p>

Most developers and development teams have one cache invalidation strategy i.e. clear all cache. And that is not a good idea for complex websites and applications which have a huge amount of content. This custom cache invalidation strategy will help you clear only the required cache and keep the rest intact. This can boost the performance a lot and goes without saying, you have a better control of your site's cache.

Latest Blogs

why choose drupal for ecommerce website

7 Reasons Why Should You Choose Drupal for eCommerce Website

E-commerce websites are increasingly using the newest technologies to enhance their outreach and customer service.

Read More

landing page call to action tactics to boost conversions rates

What Makes a Good Landing Page Call to Action?

You must be aware that apart from content, the landing page call to action is another crucial component that can drive the visitors to perform an action you desire.

Read More

need of interactive website design

Why Do You Need Interactive Website Design?

Interactive websites help in engaging more users. For example, watching a popup video, solving a puzzle or quiz, or viewing compelling infographics can make users spend more time on your website.

Read More

email marketing software for ecommerce

Best Email Marketing Software for eCommerce to Boost Sales

Is email marketing still the best marketing method? How much is it successful for the eCommerce platform?

Read More