Drupal: Blocks and Block Layout
|
This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production. This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages. |
A block is a rectangle of output that a theme places inside one of its regions: the site logo, the main navigation, the breadcrumb trail, a search form, a list of upcoming events, a paragraph of opening hours. Even the main page content is a block. This page explains how regions, placements and blocks fit together, which screens and configuration objects are involved, how visibility conditions decide where a block appears, what a content block is and how block types and the block library work, how the 11.4 permission for viewing unpublished block content behaves, why a placed block is configuration while the text inside a content block is content, and how that difference shapes deployments.
It was written against Drupal 11.4.x, with Drush 13 or 14 and PHP 8.3 or later, and the facts were checked against the
official documentation on 2026-10-30. The running example is the Meridian Museum: the meridian theme, a content block
"Opening hours" created by editors, and a block plugin "On display now" provided by the module meridian_collection.
Drupal 12.0.0 is scheduled for the week of 2026-12-07 and the end of life of Drupal 10 for 2026-12-09, so both are
described as upcoming.
Who works with blocks
-
Editors create and edit content blocks (a notice banner, the opening hours) in the block library.
-
Site builders place blocks into regions on the Block layout page and decide on which pages, for which roles and in which language each one shows.
-
Administrators grant the block permissions and decide which roles may see unpublished block content.
-
Themers declare the regions in the theme’s
*.info.ymlfile and style the output of each block. -
Developers write block plugins, alter or restrict block access and ship block placements as configuration.
The vocabulary: block, region, placement, plugin, block type, content block
The word "block" is used for several different things, and most confusion in tutorials comes from mixing them up. Since Drupal 10.1 the administration screens use these terms consistently.
| Term | What it is | Where it lives |
|---|---|---|
Region |
A named area of the page that a theme declares, such as |
The |
Block plugin |
PHP code that builds one kind of block (the "Powered by" text, the main menu, a search form, "On display now"). |
A class in |
Block placement |
One configured instance of a block plugin in a region of one theme, with its title, weight and visibility rules. |
A |
Block type |
A bundle of the content entity type |
A |
Content block |
One piece of editor-authored content (a description plus the fields of its block type), stored in the database. It is displayed through a block plugin that is derived from it. |
A |
Block library |
The administration list of reusable content blocks. |
|
The first two rows are about code, the third is configuration, and the last three are about content that editors own. Keeping those three layers apart is the key to understanding the rest of this page.
Regions
What a region is
The Drupal User Guide defines a region as a named area of a theme in which blocks can be placed; typical examples are a header, a content area and one or more sidebars. Regions belong to the theme, not to the site. A different theme has different regions, and a block is always placed in a region of one specific theme.
A theme declares its regions in its *.info.yml file. A theme that declares none receives a default set that includes
page_top, page_bottom, header, primary_menu, secondary_menu, highlighted, help, content, sidebar_first,
sidebar_second and footer. The meridian theme generated with Starterkit declares its own:
name: Meridian
type: theme
description: Theme of the Meridian Museum website.
core_version_requirement: ^11.3
base theme: false
regions:
header: Header
primary_menu: Primary menu
highlighted: Highlighted
breadcrumb: Breadcrumb
content: Content
sidebar: Sidebar
footer_top: Footer top
footer_bottom: Footer bottom
The label is what the Block layout page shows; the key (sidebar) is the region’s machine name and appears in the
configuration of every block placed there. The page_top and page_bottom regions exist so that modules can add output
(an announcement, a toolbar) outside the page template; core adds them even when a theme lists none. The full syntax, the
Content region rules and the way a region is rendered by region.html.twig are covered in
Theme info files and regions.
Rules and pitfalls with regions
-
Only the
contentregion is required. A theme without it cannot display the main page content block. -
Renaming or deleting a region in
meridian.info.ymlorphans every placement that used it. The block configuration still names the old region, but nothing renders it, so the block silently disappears from the page. After a change, open the Block layout page, move the affected blocks, and clear caches withdrush cache:rebuild. -
A region is rendered only when it has at least one visible block. An empty region produces no wrapper element, so Twig conditions such as
{% if page.sidebar %}work. Do not use a region to reserve space: use CSS. -
The sidebar’s meaning is a theme decision.
sidebar_firstis not "the left sidebar" in a right-to-left language; styling decides what the side is. -
From Drupal 12 the Olivero, Claro and Stable 9 themes leave core, so a site whose Block layout was built on Olivero has to place its blocks again in the theme it moves to. Placements are per theme and are never translated from one theme to another.
The Block layout page
Per-theme placement
The Block layout page is at /admin/structure/block (Manage, Structure, Block layout). It shows one tab for each
installed theme, plus the admin theme, and starts on the default theme. The User Guide states the rule plainly: block
placement is set per theme. A block placed in the meridian theme does not appear if a visitor is shown another theme,
and the administration pages, which use the admin theme, have their own set of placements.
The URL pattern for a tab is /admin/structure/block/list/<theme>. The page lists every region of that theme as a section
that contains its blocks in order, with a Place block button next to the region name. Below the last region is a
Disabled section for blocks that exist but are switched off.
When a theme is installed for the first time, Drupal copies the block placements of the default theme to it so the new theme does not start with empty regions. Those copies are independent from then on. Editing the placement in one theme does not change the other, which is the reason a site can look right in the default theme and broken in a theme that a subset of users is switched to.
# List every block placement of the meridian theme as configuration names
ddev drush config:list | grep '^block.block.meridian_'
# Look at one placement
ddev drush config:get block.block.meridian_opening_hours
Placing a block step by step
The User Guide’s steps still describe the screen:
-
Go to Manage, Structure, Block layout and select the tab for the theme (here
meridian). -
Find the region (for example Sidebar) and click Place block.
-
In the Place block dialog, find the block, which can be a block plugin or a content block, and click Place block.
-
Fill in the Configure block form and click Save block.
-
Confirm the block appears in the region and click Save blocks at the bottom of the page.
Behind the dialog the route is /admin/structure/block/add/<plugin_id>/<theme>, with ?region=<region> pre-selecting the
region. The Configure block form holds these settings, which the User Guide lists as title, display title, region and page
visibility:
| Setting | Effect | Configuration key |
|---|---|---|
Title |
The text shown as the block heading. Required, and used by default as the administrative label. |
|
Display title |
Whether the title is rendered. A hidden title still labels the block in the administration UI. |
|
Region |
The region of this theme where the block renders. |
|
Visibility |
Conditions that must all pass for the block to render (see the next section). |
|
Weight |
Order inside the region. Lower weights come first. Dragging rows on the Block layout page, or Show row weights, changes it. |
|
To remove a block from the page, the Operations menu offers Disable and Remove. Disable keeps the placement (the block moves
to the Disabled section and status becomes false), which is easy to reverse. Remove deletes the placement
configuration entity, so you must place the block again. Changes may need a cache clear to appear if a custom render
cache is involved; ordinary placement changes invalidate the right cache tags themselves.
Contextual links and Settings Tray
On pages rendered in the front-end theme, a user with the right permission sees a pencil icon on a block that links to Configure block. Contextual Links is a core module and is on by default in the Standard profile. The off-canvas editing feature of the Settings Tray module is scheduled to move to contrib in Drupal 12, so do not teach editors to depend on it; the Block layout page is the stable path.
The block configuration entity
Every placement is a configuration entity of type block. Its machine name defaults to the theme plus the plugin, such as
meridian_opening_hours. This is the file that drush config:export writes for the "Opening hours" content block placed
in the sidebar of the museum’s front-end theme:
uuid: 7d1d6a38-0e5f-4a2a-9a3a-0e3b3f2c1a10
langcode: en
status: true
dependencies:
content:
- 'block_content:basic:5b2a9f5c-6a41-4d0e-8d6e-2b1a8c0d7e21'
module:
- block_content
theme:
- meridian
id: meridian_opening_hours
theme: meridian
region: sidebar
weight: -5
provider: null
plugin: 'block_content:5b2a9f5c-6a41-4d0e-8d6e-2b1a8c0d7e21'
settings:
id: 'block_content:5b2a9f5c-6a41-4d0e-8d6e-2b1a8c0d7e21'
label: 'Opening hours'
label_display: visible
provider: block_content
status: true
info: ''
view_mode: full
visibility:
request_path:
id: request_path
negate: false
pages: "/visit\n/visit/*"
Source: Configuration schema metadata
The important keys:
-
themeandregionbind the placement to one theme and one region of that theme. Thethemeentry independenciesmeans that uninstalling the theme deletes the placement. -
pluginis the block plugin ID. For a content block it isblock_content:followed by the content block’s UUID, which is why the file also lists the content entity underdependencies.content. -
settingsis the configuration of the plugin itself and is stored by the plugin’sblockForm()/blockSubmit()code. Theinfoandstatussettings of content-block plugins were deprecated in 11.3 because nothing used them, and the change record offers no replacement; do not write code that relies on them. -
visibilityholds the condition plugins, keyed by plugin ID.
Because the placement is configuration, it travels with configuration management: it is exported, reviewed in a pull request,
imported on staging and production, and overridable in settings.php as $config['block.block.meridian_opening_hours'].
See Configuration management and
the Configuration API and schema.
Visibility conditions
The condition plugins in the UI
The Visibility section of the Configure block form is built from condition plugins (#[Condition]), and a block renders
only when all enabled conditions pass. A condition can be negated. The tabs shown depend on the installed modules:
| Tab | Plugin ID | Configuration | Notes |
|---|---|---|---|
Pages |
|
|
Wildcard |
Content types |
|
|
Shown only on a node page of the selected types, including the node’s own full page. Needs the route to carry a node. |
Roles |
|
|
Shown to a user who has any of the checked roles. No role checked means no restriction. |
Languages |
|
|
Present when the Language module is on; matches the content or interface language of the request. |
A block placed with no condition is displayed on every page of the theme. The page match is the one that most often
surprises site builders: a path pattern is matched against the alias, so /visit/* works only while the aliases of the
child pages start with /visit/. Aliases and the Pathauto-style patterns are in
Menus and path aliases.
The condition plugins are the same API that Views and other modules use for "show when" logic, and their configuration
appears in the visibility key of the exported block, as seen in the previous example. A configured visibility condition
adds cache contexts to the block: the path condition adds url.path, the role condition user.roles, the content-type
condition a route context. This is how Drupal keeps the page cache correct without any extra work, and why a block
visible only to curators does not leak into the cached page of anonymous visitors.
Visibility in code
The simplest way to restrict a block in code is hook_block_access(), which in Drupal 11.1 and later is an object-oriented
hook. This module hides the "Opening hours" block on the exhibition detail pages, where the exhibition already shows its own
hours:
<?php
declare(strict_types=1);
namespace Drupal\meridian_collection\Hook;
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Block-related hook implementations of the Meridian collection module.
*/
final class BlockHooks {
public function __construct(
private readonly RouteMatchInterface $routeMatch,
) {}
/**
* Implements hook_block_access().
*/
#[Hook('block_access')]
public function blockAccess(Block $block, string $operation, AccountInterface $account): AccessResultInterface {
if ($operation !== 'view' || $block->id() !== 'meridian_opening_hours') {
return AccessResult::neutral();
}
$node = $this->routeMatch->getParameter('node');
if ($node instanceof NodeInterface && $node->bundle() === 'exhibition') {
// The result depends on the route, so say so.
return AccessResult::forbidden('Exhibition pages show their own hours.')
->addCacheContext('route');
}
return AccessResult::neutral()->addCacheContext('route');
}
}
Returning neutral() leaves the decision to the other conditions, forbidden() always wins. Every access result must declare
the cache contexts it depends on (here route), or the block’s decision is cached for the wrong pages. A block plugin can
also restrict itself in its blockAccess() method, shown below. More on access results is in
Access control in code, and the OOP hook syntax, including
#[LegacyHook] for 10.x compatibility, is in Hooks and OOP hooks.
Content blocks
Block types and content blocks
A content block is a piece of editor-authored content. It is the block_content content entity type, and like a node it has
bundles, called block types, each with its own fields and form and display modes. The 10.1 change record "Block
administration page paths have changed" fixed the vocabulary and the screens:
-
custom block types became block types, managed under Structure, Block types (the change record moved them from
/admin/structure/block/block-content/typesto a screen reached from the Structure page); -
custom blocks became content blocks, listed in the block library at
/admin/content/blockunder Content, Blocks and edited at/admin/content/block/<id>; -
the module was renamed from Custom Block to Block Content, and the old paths redirected until Drupal 11.
Older books such as Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) describe the pre-10.1 screens
and wording ("custom block", "custom block library", Structure, Block layout, Custom block library). Today the same
operations live under Content, Blocks (the library) and Structure, Block types, and the User Guide chapter on blocks uses
the new terms. Treat any tutorial that refers to the old block-content paths under admin/structure/block as predating 10.1.
The User Guide’s example block has two fields: a block description (an administrator-only name) and a body that visitors see. With Drupal 11, creating a type adds other fields as well, using the same field UI as content types; see Content types and fields, Field types and entity references and Display modes and image styles.
# config/sync/block_content.type.notice_banner.yml
langcode: en
status: true
dependencies: { }
id: notice_banner
label: 'Notice banner'
revision: 0
description: 'A short notice with an optional call to action, shown across the museum site.'
Because a block type is a bundle, fields added to it (a field_link, a field_image media reference) are configuration
too, so they travel with the type while the content blocks that use them do not.
Creating a content block
The User Guide’s steps for the library:
-
Go to Manage, Content, Blocks (
/admin/content/block). -
Click Add content block, and if more than one block type exists, choose the type.
-
Fill in the block description and the other fields.
-
Click Save.
Editing a block means returning to the same list and clicking Edit. The block is not on any page yet: it only becomes visible when it is placed in a region, as described earlier. The Place block dialog lists content blocks next to the block plugins under their description.
Reusable and non-reusable content blocks
A block_content entity has a reusable base field.
-
A reusable block appears in the block library and can be placed on the Block layout page and in Layout Builder as often as needed. Editing it once changes every place that shows it.
-
A non-reusable block belongs to one parent. Layout Builder creates these when a builder adds an inline block to a single page layout. They do not appear in the library, have no placement and are removed with their parent entity.
This difference explains two behaviours people report as bugs: why the library lists fewer blocks than the database contains, and why deleting a page removes its inline blocks. See Layout Builder and Canvas visual page building for the page-level alternatives. Reusable blocks are a good match for site-wide text (opening hours, a newsletter pitch); a layout of one page is not.
Revisions, status and the unpublished state
Content blocks are revisionable, translatable and publishable entities. The status field makes a block published or
unpublished, and Content Moderation can be attached to the block type so that a banner follows a draft, review and published
workflow like content does. An unpublished block does not render for visitors. See
Content moderation, workflows and revisions for the Meridian
workflow and Multilingual for translating the block body and the placement title.
The permissions of the block library
| Permission | Allows |
|---|---|
|
Place, configure and remove blocks on the Block layout page; the Block module’s permission, restricted. |
|
Create, edit and delete all content blocks, and see them even when unpublished. |
|
Manage the block types and their fields and displays at Structure, Block types. |
|
Open the block library list at |
|
View unpublished content blocks without the broader permissions (new in 11.4, see below). |
Only the first of these touches the layout of the whole site, so grant it to the Administrator role only; a person who can place blocks and write the markup of a block can publish a script to every page, depending on the text format.
The "view unpublished block content" permission (11.4)
Drupal 11.4 adds the permission view unpublished block content, which belongs to the Block Content module. The change
record is titled "New permission available to view unpublished block content" and is marked as introduced in 11.4.0. The
issue behind it records that the change was committed to the 11.4.x branch on 2026-06-03. Before it, an editor needed
either administer block content or access block library to see an unpublished content block. That was a problem because
blocks became publishable: a block saved as a draft under Content Moderation was invisible to an editor who could create and
edit blocks and could even place it through a plugin or an inline block in Layout Builder, but could not see it.
The access check lives in the entity access handler BlockContentAccessControlHandler. For the view operation, the result
is allowed when the block is published, or when the account has access block library or the new permission (the first
check, administer block content, is unchanged). The result carries the entity as a cacheable dependency, so it is
invalidated when the block is saved. The JSON:API and REST error messages changed with it: reading an unpublished block
without the permissions now reports that access block library or view unpublished block content is required.
For the Meridian Museum, the Content Editor role gets the new permission so editors can preview a draft banner without being able to open the whole library:
# config/sync/user.role.content_editor.yml (excerpt)
langcode: en
status: true
dependencies:
module:
- block_content
- node
id: content_editor
label: 'Content Editor'
weight: 3
is_admin: false
permissions:
- 'access content overview'
- 'create exhibition content'
- 'edit own exhibition content'
- 'view unpublished block content'
Pitfalls and version notes:
-
The permission is new in 11.4 and does not exist in 10.x. A role configuration exported from 11.4 that lists it fails to import into an older site, and a configuration with an unknown permission is flagged by the config schema checks, so upgrade the target site first.
-
The permission allows viewing only. Editing and deleting still need
administer block content. -
The rendered page is a separate question from entity access: an unpublished block that is placed on the Block layout page is not shown to visitors.
-
Search for the permission name in custom code and configuration: modules that checked
access block libraryto guess whether a user may see unpublished blocks should now check the entity ($block->access('view')), not the permission.
Block plugins
A content block is only one source of blocks. Everything else comes from block plugins. Core ships plugins for the main
page content, site branding, menus, the page title, messages, search, "Powered by" and more; Views exposes each Block
display as a plugin; Layout Builder adds field blocks and inline blocks; modules add their own. Since Drupal 10.2 plugins are
discovered through PHP attributes, and the Drupal\Core\Block\Attribute\Block attribute takes the plugin id, an
admin_label, an optional category, context_definitions for required context, and optional deriver and forms
arguments. Annotations still work but are on a deprecation path.
Older books such as Drupal 10 Development Cookbook (2023) show the @Block(...) annotation in a doc comment. Write the
attribute instead; the class otherwise remains the same. The plugin API in general, with discovery and derivers, is in
the Plugin API and attributes.
The "On display now" block of the Meridian Museum lists the collection objects currently on display:
<?php
declare(strict_types=1);
namespace Drupal\meridian_collection\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the "On display now" block.
*/
#[Block(
id: 'meridian_on_display_now',
admin_label: new TranslatableMarkup('On display now'),
category: new TranslatableMarkup('Meridian'),
)]
final class OnDisplayNowBlock extends BlockBase implements ContainerFactoryPluginInterface {
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
private readonly EntityTypeManagerInterface $entityTypeManager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
);
}
public function defaultConfiguration(): array {
return ['count' => 5] + parent::defaultConfiguration();
}
public function blockForm($form, FormStateInterface $form_state): array {
$form['count'] = [
'#type' => 'number',
'#title' => $this->t('Number of objects'),
'#default_value' => $this->configuration['count'],
'#min' => 1,
'#max' => 20,
];
return $form;
}
public function blockSubmit($form, FormStateInterface $form_state): void {
$this->configuration['count'] = (int) $form_state->getValue('count');
}
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'access content');
}
public function build(): array {
$storage = $this->entityTypeManager->getStorage('collection_object');
$ids = $storage->getQuery()
->accessCheck(TRUE)
->condition('on_display', TRUE)
->sort('accession_number')
->range(0, (int) $this->configuration['count'])
->execute();
$objects = $storage->loadMultiple($ids);
return [
'#theme' => 'item_list',
'#items' => array_map(
static fn ($object) => $object->toLink()->toRenderable(),
$objects,
),
'#empty' => $this->t('Nothing is on display at the moment.'),
'#cache' => [
'tags' => ['collection_object_list'],
'contexts' => ['user.permissions'],
],
];
}
public function getCacheTags(): array {
return Cache::mergeTags(parent::getCacheTags(), ['collection_object_list']);
}
}
Source: Block API overview
The main methods are defaultConfiguration(), blockForm() and blockSubmit() for the settings stored under
settings in the placement, blockAccess() for the plugin’s own access check, and build() for the render array. The
block’s cache metadata must describe what the output depends on: the entity query uses accessCheck(TRUE), so the result
varies by user permissions, and the list changes when any collection_object is saved, hence the tag. More on cache tags,
contexts and max-age is in the Render API and cache metadata and
Performance and caching. Constructor injection and
autowiring are explained in Services and dependency injection;
only code that has no service container, such as a procedural file, should use \Drupal:: static calls.
Block plugins that need a node or a user declare context_definitions in the attribute and read them with
getContextValue(); a block that is set up this way is not offered where the context is not available. Plugins that
should be offered in fields rather than regions are described in
Block field, widget and formatter plugins.
Configuration versus content
What is exported and what is not
A placed block is a configuration entity, the content block it shows is a content entity. This is the most important operational fact about blocks:
| Object | Kind | Travels with | Environment-specific? |
|---|---|---|---|
Region list |
Theme code |
The theme in version control |
No |
Block placement ( |
Configuration |
|
Settings can be overridden per environment |
Block type ( |
Configuration |
Config export |
No |
Content block ( |
Content |
Database copy, default content, a migration or a recipe’s content |
Yes: each environment has its own rows |
Block plugin code |
Code |
Composer and Git |
No |
The consequence appears the first time a placed content block is deployed. The exported placement lists the content block’s
UUID as a dependency (block_content:basic:5b2a...). Importing the configuration on production, where that block does not
exist yet, fails with a missing dependency or leaves a placement that renders nothing. The ways out are to create the content
block on production by hand and keep the UUID by synchronising content, to deploy the block as default content or with a
recipe or migration that carries content, or to avoid the dependency by replacing the content block with a block plugin whose
text is a configuration setting or a translatable string.
Choose by who owns the text. Opening hours, notices and campaign copy belong to editors, change often and should not need a deployment: use content blocks, and export the placement only. A fixed footer disclaimer or a "Powered by" line belongs to developers: use a block plugin or a template, so it is reviewed in a pull request. The reasoning for staging is in Configuration management and Deployment and CI/CD.
Applying placements with a recipe
A recipe can ship block placements for a fresh site. The meridian_events recipe imports its configuration from config/ and
can adjust a placement that already exists through a config action:
# recipes/meridian_events/recipe.yml (excerpt)
name: 'Meridian events'
description: 'Event content type, fields, view and the upcoming events block.'
type: 'Site'
install:
- block
- block_content
config:
import:
block: ['block.block.meridian_upcoming_events']
actions:
block.block.meridian_upcoming_events:
setRegion: sidebar
setWeight: -3
Placement configuration depends on the theme, so a recipe that places a block in meridian depends on that theme being
installed. See Recipes for how imports, actions and ordering work.
Common errors and pitfalls
-
The block does not appear. Check, in order: the placement is not in the Disabled section; the region exists in this theme; the visibility conditions pass for this URL (aliases, not internal paths, are matched) and role; the block is published and the viewer has access; and render caches have been cleared if the block’s own cache metadata is wrong.
-
Placed in one theme but not the other. Placements are per theme, so a front-end block is missing on the admin theme and the other way round.
-
A block disappeared after changing the theme’s regions. The placement names a region the theme no longer declares.
-
An editor cannot see a draft block. Grant
view unpublished block contenton 11.4, oraccess block libraryon earlier versions. -
Different content on staging and production. Content blocks are not configuration. Synchronise them as content.
-
Stale output. A custom plugin returned a render array without cache tags, contexts or
max-age. Fix the metadata rather than disabling the cache. -
A required visibility condition has no effect. Node-based conditions need a node route; on a Views page the node is not in the route, so use a path condition.
-
The block shows to the wrong people. Visibility is not security. A role condition hides output from the page but not the data behind it: protect sensitive information with entity or plugin access, not just a placement rule.
Security
-
Restrict
administer blocks,administer block contentandadminister block typesto trusted roles. Block bodies use text formats, so a role that can pick Full HTML in a block can inject script into every page where the block appears. Content editors should use a restricted format; see Text formats and CKEditor 5. -
Use access results and cache contexts together.
forbidden()without the matching context is a cache bug that hides or reveals a block for the wrong visitor. -
Query data with
accessCheck(TRUE)in a block whose output depends on the viewer. -
Never put credentials in block settings. A block form setting is stored in exported configuration; use the environment or the Key module instead.
Older advice and Drupal 12
-
Pre-10.1 tutorials and books use "custom block" and "custom block library". Use Content, Blocks and Structure, Block types.
-
Annotation-based
@Blockplugins and.modulehook functions such ashook_block_access()still work in 11.x, but the attribute and the#[Hook]class method are the current form. -
The deprecated
statusandinfosettings of content block plugins (11.3) must not be used in new code. -
Drupal 12.0.0 is scheduled for the week of 2026-12-07. It removes the Claro, Olivero and Stable 9 themes, moves Settings Tray and Toolbar to contrib and requires a site to be on 11.3 or later before updating, so re-check the placements of the themes you use and install the contrib modules you rely on before updating.
-
Drupal CMS 2.x edits pages with Canvas rather than the Block layout page, though the Block layout page and content blocks remain in core. Canvas is described in Canvas visual page building.
Summary
Regions come from the theme, plugins come from modules, content blocks come from editors, and a placement is the configuration that joins one of them to a region of one theme under visibility conditions. Keep placements in configuration, text that editors own in content blocks, and grant the new 11.4 permission when editors must preview unpublished blocks. The Meridian theme’s regions are continued in Theme info files and regions, and taxonomy-driven lists in Taxonomy.
References
-
Change record: New permission available to view unpublished block content
-
Drupal issue: Add a permission to view unpublished block content
-
Change record: Block administration page paths have changed (10.1.0)
-
Change record: status and info settings in block_content blocks are deprecated (11.3.0)
-
Change record: Support for object oriented hook implementations