Block Access Control
When to Use
Controlling whether a block should be displayed based on user permissions, roles, or custom logic.
Steps
-
Override blockAccess() method
use Drupal\Core\Access\AccessResult; use Drupal\Core\Session\AccountInterface; protected function blockAccess(AccountInterface $account) { return AccessResult::allowedIfHasPermission($account, 'access content'); } -
Return AccessResult object
AccessResult::allowed()— Show the blockAccessResult::forbidden()— Hide the block (uncacheable)-
AccessResult::neutral()— No opinion (default to other checks) -
Add cache metadata for dynamic access
return AccessResult::allowedIfHasPermission($account, 'edit own content') ->addCacheContexts(['user']); -
Combine multiple conditions
return AccessResult::allowedIf($condition1 && $condition2) ->addCacheTags(['node:1']);
Decision Points
| At this step... | If... | Then... |
|---|---|---|
| Step 1 (access check) | Based on permission | Use AccessResult::allowedIfHasPermission() |
| Step 1 (access check) | Based on role | Check $account->hasRole(), add user.roles context |
| Step 1 (access check) | Based on content/entity | Add cache tags for that entity |
| Step 2 (return) | Access changes frequently | Use forbidden() sparingly; it's uncacheable |
| Step 3 (caching) | User-dependent | Add user or user.roles cache context |
Pattern
Common access patterns:
// Permission-based
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'access content');
}
// Role-based with cache context
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIf($account->hasRole('premium_member'))
->addCacheContexts(['user.roles']);
}
// Anonymous users only
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIf($account->isAnonymous())
->addCacheContexts(['user.roles:anonymous']);
}
// Complex logic with multiple cache metadata
protected function blockAccess(AccountInterface $account) {
$node = \Drupal::routeMatch()->getParameter('node');
$access = AccessResult::allowedIf(
$node && $node->bundle() === 'article' && $account->hasPermission('view articles')
);
return $access->addCacheContexts(['route', 'user.permissions'])
->addCacheTags($node ? $node->getCacheTags() : []);
}
Reference: core/modules/user/src/Plugin/Block/UserLoginBlock.php (lines 85-92)
Common Mistakes
- Using
forbidden()whenneutral()is appropriate →forbidden()prevents caching; useneutral()to defer to other systems - Forgetting cache contexts on dynamic access → Block will show incorrectly for different users
- Checking access in
build()instead ofblockAccess()→ Bypasses access control system and caching - Not returning
AccessResultobject → Must returnAccessResult, not boolean - Using
allowed()when should useallowedIf($condition)→allowed()always grants access regardless of condition
See Also
- Block Caching Strategies
- Visibility Conditions (for UI-configurable access)
- Reference: https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/access-checking-on-routes