Drupal's Entity Query API — the object-oriented way to ask "find me all published articles by this author" without writing raw SQL — sits on top of the same database tables you've been querying directly. Because it's built on the Cache API you've already met elsewhere in this course, an entity query's result can be cached automatically, but only if you set it up correctly. Get this wrong and you'll either serve stale content or, just as commonly, get zero caching benefit while believing you have some.
What you'll learn in this lesson
- Why entity queries themselves aren't cached by default — only specific things you build around them are
- How to cache an entity query's result with proper cache tags so it invalidates correctly
- A common mistake that silently defeats entity query caching entirely
Entity queries are not automatically cached
$nids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->accessCheck(TRUE)
->execute();
This runs a real, fresh SQL query every single time it executes — Drupal does not cache entity query results for you automatically. What is cached automatically, if you use the Entity API correctly afterward, is the rendered output of the entities you load with those IDs — that's a render-cache concern, not a query-cache one. If you want the query result itself cached, you build that caching yourself, the same way you would for any other expensive computation.
Caching a query result with the right cache tags
$cid = 'performance_example:published_article_nids';
if ($cache = \Drupal::cache()->get($cid)) {
$nids = $cache->data;
}
else {
$nids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->accessCheck(TRUE)
->execute();
\Drupal::cache()->set($cid, $nids, Cache::PERMANENT, ['node_list:article']);
}
The fourth argument to set() — ['node_list:article'] — is the cache tag. Drupal core itself invalidates the node_list:article tag automatically whenever any article node is created, updated, or deleted, which means this cached ID list gets thrown away and rebuilt exactly when it needs to be, and stays valid the rest of the time. Skip the tag and you're stuck choosing between never-updating stale data or manually clearing the cache yourself on every content change — both worse than letting Drupal's own tag-invalidation system do the job.
accessCheck() considerations. A cached list of node IDs computed once for one user's permissions can leak into a request from a user with different access — cache entity-query-derived data per relevant cache context (like user.permissions) if the results genuinely differ by permission, or run accessCheck(FALSE) deliberately and enforce access separately when loading the actual entities.Quick check: you cache an entity query's result with the tag
node_list:article, then a site editor publishes a new article. Does your cached ID list update automatically, or does someone need to clear caches by hand? (Automatically — publishing a node invalidatesnode_list:articleas part of Drupal core's normal save process, which is exactly why tagging the cache entry with it, rather than leaving it untagged or relying on a fixed expiration, is the correct approach.)
Key takeaways
- Entity queries run a real, fresh database query every time — there's no automatic result caching built into
entityQuery()itself. - If you need a query result cached, wrap it yourself with
\Drupal::cache()->get()/set()and real cache tags, not a fixed time-based expiration alone. node_list:*-style list tags are invalidated automatically by Drupal core on relevant content changes — use them rather than reinventing invalidation logic.- Be deliberate about
accessCheck()when the query result will be cached and potentially shared across different users' permissions.
Coming up next
Caching helps once a query is already reasonably efficient — but a query with unnecessary joins or nested subqueries can be slow no matter how well you cache it. Next, we look at trimming dynamic queries down to exactly what they need.