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.yml file 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 header, content or sidebar.

The regions key of the theme’s *.info.yml file.

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 src/Plugin/Block of a module, discovered through the #[Block] attribute.

Block placement

One configured instance of a block plugin in a region of one theme, with its title, weight and visibility rules.

A block configuration entity, exported as block.block.<id>.yml.

Block type

A bundle of the content entity type block_content: it defines which fields a content block of that kind has.

A block_content_type configuration entity, exported as block_content.type.<id>.yml.

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_content content entity.

Block library

The administration list of reusable content blocks.

/admin/content/block, under 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.

flowchart TB T[Theme info file<br/>declares regions] --> R[Region in a page] P[Block plugins<br/>module code] --> B C[(Content block<br/>block_content entity)] --> D[Derived plugin<br/>block_content:UUID] D --> B B[Block placement<br/>block config entity<br/>theme, region, weight, visibility] --> R V{Visibility<br/>conditions and<br/>block access} -->|pass| B

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 content region is required. A theme without it cannot display the main page content block.

  • Renaming or deleting a region in meridian.info.yml orphans 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 with drush 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_first is 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:

  1. Go to Manage, Structure, Block layout and select the tab for the theme (here meridian).

  2. Find the region (for example Sidebar) and click Place block.

  3. In the Place block dialog, find the block, which can be a block plugin or a content block, and click Place block.

  4. Fill in the Configure block form and click Save block.

  5. 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.

settings.label

Display title

Whether the title is rendered. A hidden title still labels the block in the administration UI.

settings.label_display

Region

The region of this theme where the block renders.

region

Visibility

Conditions that must all pass for the block to render (see the next section).

visibility

Weight

Order inside the region. Lower weights come first. Dragging rows on the Block layout page, or Show row weights, changes it.

weight

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.

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/*"

The important keys:

  • theme and region bind the placement to one theme and one region of that theme. The theme entry in dependencies means that uninstalling the theme deletes the placement.

  • plugin is the block plugin ID. For a content block it is block_content: followed by the content block’s UUID, which is why the file also lists the content entity under dependencies.content.

  • settings is the configuration of the plugin itself and is stored by the plugin’s blockForm()/blockSubmit() code. The info and status settings 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.

  • visibility holds 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

request_path

pages, a list of paths one per line

Wildcard *; <front> stands for the front page. Compared against the path alias of the current request, and the internal path is also checked. Show for the listed pages, or hide for the listed pages.

Content types

entity_bundle:node

bundles

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

user_role

roles

Shown to a user who has any of the checked roles. No role checked means no restriction.

Languages

language

langcodes

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/types to a screen reached from the Structure page);

  • custom blocks became content blocks, listed in the block library at /admin/content/block under 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:

  1. Go to Manage, Content, Blocks (/admin/content/block).

  2. Click Add content block, and if more than one block type exists, choose the type.

  3. Fill in the block description and the other fields.

  4. 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

administer blocks

Place, configure and remove blocks on the Block layout page; the Block module’s permission, restricted.

administer block content

Create, edit and delete all content blocks, and see them even when unpublished.

administer block types

Manage the block types and their fields and displays at Structure, Block types.

access block library

Open the block library list at /admin/content/block and see every block in it, published or not.

view unpublished block content

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 library to 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']);
  }

}

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 (block.block.*)

Configuration

drush config:export and config:import

Settings can be overridden per environment

Block type (block_content.type.*) and its fields

Configuration

Config export

No

Content block (block_content entity)

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 content on 11.4, or access block library on 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 content and administer block types to 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 @Block plugins and .module hook functions such as hook_block_access() still work in 11.x, but the attribute and the #[Hook] class method are the current form.

  • The deprecated status and info settings 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.