Drupal cache contexts implementation guide. Use when asked about request-based cache variations, user.roles vs user context, URL contexts, language contexts, custom cache contexts, or cache context hierarchy. Helps prevent cache explosion from overly broad contexts.
Cache contexts define request-dependent cache variations. Analogous to HTTP Vary header.
| 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) |
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.
Input: "I show different content to editors vs anonymous users"
Output:
$build = [
'#markup' => $this->getRoleBasedContent(),
'#cache' => [
'contexts' => ['user.roles'],
],
];
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,
];
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'],
],
];
Input: "I render translated labels in my block"
Output:
$build = [
'#markup' => $this->t('Welcome'),
'#cache' => [
'contexts' => ['languages:language_interface'],
],
];
Input: "Content varies by role AND language"
Output:
$build = [
'#markup' => $this->getLocalizedRoleContent(),
'#cache' => [
'contexts' => [
'user.roles',
'languages:language_interface',
],
],
];
Input: "I need to vary cache by a custom HTTP header"
Output:
# my_module.services.yml