drupal-cache-contexts
SKILL.md
Drupal Cache Contexts
Cache contexts define request-dependent cache variations. Analogous to HTTP Vary header.
When to Use
- Content varies by user role or permissions
- Content changes based on URL parameters
- Multi-language sites with localized content
- Theme-dependent rendering
- Preventing cache explosion from overly broad contexts
Available Contexts
| Context | Variations | Use Case |
|---|---|---|
user |
Per-user (AVOID) | Truly personalized content only |
user.roles |
Per-role combination | Role-based visibility |
user.permissions |
Per-permission set | Permission-based content |
url.path |
Per-path | Path-dependent content |
url.query_args:key |
Per-parameter | Sort, filter, pagination |
languages:language_interface |
Per-language | Translated content |
theme |
Per-theme | Theme-specific rendering |
session |
Per-session | Session data (triggers placeholder) |
Context Hierarchy
user (AVOID - per-user cache explosion)
├── user.is_super_user (2 variations only)
├── user.roles (per role combination)
│ └── user.roles:editor (has specific role?)
└── user.permissions (per permission set)
Rule: Always use the most specific context possible.
Examples
Example 1: Role-Based Content
Input: "I show different content to editors vs anonymous users"
Output:
$build = [
'#markup' => $this->getRoleBasedContent(),
'#cache' => [
'contexts' => ['user.roles'],
],
];
Example 2: Per-User Content (Use Sparingly)
Input: "I need to show the user's own profile info"
Output:
// WARNING: Creates cache entry per user - use lazy_builder instead
$build = [
'#markup' => $user->getDisplayName(),
'#cache' => [
'contexts' => ['user'], // High cardinality!
],
];
// BETTER: Use lazy builder for per-user content
$build = [
'#lazy_builder' => ['my_module.lazy:userName', []],
'#create_placeholder' => TRUE,
];
Example 3: URL Query Parameters
Input: "My listing supports ?sort=date and ?sort=title parameters"
Output:
$sort = \Drupal::request()->query->get('sort', 'date');
$build = [
'#theme' => 'item_list',
'#items' => $this->getSortedItems($sort),
'#cache' => [
'contexts' => ['url.query_args:sort'],
],
];
Example 4: Language-Dependent Content
Input: "I render translated labels in my block"
Output:
$build = [
'#markup' => $this->t('Welcome'),
'#cache' => [
'contexts' => ['languages:language_interface'],
],
];
Example 5: Combined Contexts
Input: "Content varies by role AND language"
Output:
$build = [
'#markup' => $this->getLocalizedRoleContent(),
'#cache' => [
'contexts' => [
'user.roles',
'languages:language_interface',
],
],
];
Example 6: Custom Cache Context
Input: "I need to vary cache by a custom HTTP header"
Output:
# my_module.services.yml
services:
cache_context.custom_header:
class: Drupal\my_module\Cache\CustomHeaderContext
arguments: ['@request_stack']
tags:
- { name: cache.context }
namespace Drupal\my_module\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
class CustomHeaderContext implements CacheContextInterface {
public static function getLabel() {
return t('Custom header');
}
public function getContext() {
$request = $this->requestStack->getCurrentRequest();
return $request->headers->get('X-Custom-Header', 'default');
}
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}
// Usage
$build['#cache']['contexts'][] = 'custom_header';
Example 7: Block with Cache Contexts
Input: "My block shows different actions based on permissions"
Output:
class ActionBlock extends BlockBase {
public function build() {
$actions = [];
if (\Drupal::currentUser()->hasPermission('edit content')) {
$actions[] = 'Edit';
}
return ['#markup' => implode(', ', $actions)];
}
public function getCacheContexts() {
return Cache::mergeContexts(
parent::getCacheContexts(),
['user.permissions']
);
}
}
Common Mistakes
| Mistake | Impact | Solution |
|---|---|---|
Using user for role checks |
Cache explosion (1 entry per user) | Use user.roles |
Using session directly |
Triggers auto-placeholder | Use lazy builder |
| Missing context | Same cached content for all variations | Add appropriate context |
| Too broad context | Unnecessary cache variations | Use most specific context |
Auto-Placeholdering
These contexts trigger automatic placeholdering in Dynamic Page Cache:
# services.yml - default conditions
renderer.config:
auto_placeholder_conditions:
contexts:
- 'session'
- 'user'
Content with these contexts is replaced with a placeholder and rendered separately.
Debugging
# Enable debug headers
$settings['http.response.debug_cacheability_headers'] = TRUE;
# Check applied contexts
curl -sI https://site.com/ | grep X-Drupal-Cache-Contexts
# Output: X-Drupal-Cache-Contexts: languages:language_interface theme url.path user.permissions
Weekly Installs
11
Repository
sparkfabrik/sf-…-copilotGitHub Stars
2
First Seen
Jan 26, 2026
Security Audits
Installed on
codex11
opencode9
github-copilot9
gemini-cli9
amp9
cursor9