Creating a custom Drupal block with contextual visibility
Drupal blocks are reusable units of page content that can be placed in theme regions, including headers, sidebars, and footers. A useful block should carry its own markup, accessibility behaviour, and caching rules, while its visibility remains under editorial control. Contextual visibility lets the same block appear only when the current request matches a meaningful rule, rather than repeating configuration across many pages.
Context, a contributed Drupal module, provides conditions and reactions for this kind of conditional placement. This guide covers Drupal 10 and 11, using a local support panel as the example. It also shows where core visibility settings remain useful and where a custom block plugin offers more control for developers and site builders.
Understand contextual visibility
Core blocks expose familiar visibility controls in the block layout screen: request path, user roles, and language. These settings are enough for a simple teaser, but they describe the page request rather than the content or business situation behind it. A Context configuration can combine several conditions, including an internal path, a content type, a specific entity, a role, or a taxonomy term, depending on the modules and Context release installed.
Each Context contains conditions and reactions. Conditions inspect the current request and page entity; reactions then enable or configure a block in a chosen theme region. Conditions may require every selected rule to match or any one rule to match. In the example, the panel can be tied to service pages for Melbourne and Sydney, while a Brisbane campaign can use a different message. This keeps presentation rules in configuration and separates them from the block's reusable content.
Prepare Drupal and map the rules
Install and enable Context before creating the configuration. On a Composer-managed Drupal 10 or 11 site, run composer require drupal/context --update-with-dependencies, enable the module through Extend, and clear caches. Confirm the active theme exposes the region you intend to use, and check that URL aliases and node paths resolve as expected. If the project uses a custom module for the block, keep that module's version constraints compatible with the site's Drupal branch.
Write the rule as a small decision matrix before opening the admin screens. For example, the support panel might require a service content type, an authenticated visitor, and one of two city landing pages. Editorial content should use Australian English, prices should be shown in Australian dollars with GST wording where relevant, and dates should use an unambiguous day-month-year format. Decide whether the panel covers Sydney and Melbourne only, or every Australian service page, including Brisbane.
Build the reusable custom block
For a fixed panel with bespoke markup, implement a custom block plugin. Create modules/custom/local_services, add an info file and a services file, and place the plugin under src/Plugin/Block/LocalServicesBlock.php. Set type: module in the info file and use a core_version_requirement compatible with the project, such as ^10 || ^11. The service definition lets Drupal discover the block and show it in the block layout and Context reaction selectors.
<?php
namespace Drupal\local_services\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
class LocalServicesBlock extends BlockBase {
use StringTranslationTrait;
public function build() {
return [
'#type' => 'container',
'#attributes' => ['class' => ['local-services']],
'title' => [
'#type' => 'heading',
'#level' => 2,
'#text' => $this->t('Local support'),
],
'copy' => [
'#type' => 'inline_template',
'#template' => '<p>{{ text }}</p><p>{{ price }}</p>',
'#text' => $this->t('Speak with an Australian specialist during local business hours.'),
'#price' => $this->t('Standard support is $149 AUD, including GST.'),
],
'link' => [
'#type' => 'link',
'#title' => $this->t('Book a consultation'),
'#url' => ['uri' => '/contact'],
],
'#cache' => [
'contexts' => ['url.path', 'user.roles'],
],
];
}
}
Register the plugin with a block.plugin service tag:
services:
local_services.block:
class: Drupal\local_services\Plugin\Block\LocalServicesBlock
arguments: ['@string_translation']
tags:
- { name: block.plugin }
Keep the render array semantic. A heading element gives assistive technology a proper boundary, and a normal Drupal link preserves keyboard operation. If JavaScript adds behaviour, attach it through the block's library and limit selectors with the block wrapper. This prevents support panel code from affecting unrelated pages that happen to use the same class names.
Enable Local services and clear caches. The block can now be placed in a region, configured with a visibility condition, or assigned by a Context reaction. Its copy should be translatable, its link should use a valid Drupal route, and its cache contexts should include anything that changes the render. When editors need frequent copy changes, the Custom Block contributed module or a Paragraphs-based approach is usually easier to maintain than hard-coded strings.
Configure Context and attach the reaction
At /admin/config/context, add a context with a precise label, such as “Melbourne service support”. Add a path condition for the relevant internal paths and an entity condition that selects nodes in the service bundle. Where a stable audience rule is needed, add the required user role. Use the context interface's matching control carefully: “all” conditions produce narrower placement, while “any” can spread one panel across several paths.
Path conditions can cover a family of internal paths with a wildcard, but keep the pattern narrow. A pattern covering /node/* would affect every node, while a specific service landing page avoids that breadth. If separate contexts use overlapping patterns, review their order in the listing and confirm that only one matching context assigns the same region.
Open the context's reactions, choose the block reaction, and add Local services. Select the theme region and set any per-instance options, such as a heading or wrapper class. Keep the core block visibility settings separate from the Context rule to avoid two competing systems. Drupal path conditions generally evaluate internal paths, so test aliases that redirect to nodes and avoid relying on an alias where the underlying path can change.
Match Australian content and audiences
Content-driven rules are more durable than hard-coded URL fragments. The entity bundle condition can restrict the panel to service nodes regardless of how their aliases change. A taxonomy condition can match city term pages where the installed Context version supports it. Taxonomies such as Service city and Service state give editors a controlled vocabulary for Sydney, Melbourne, and Brisbane and allow related content to be grouped consistently.
Context conditions do not automatically inspect every custom Drupal field. If eligibility depends on field_service_state, field_postcode, or campaign dates, create a custom Context condition that evaluates that field and returns a boolean match. Local support hours may also differ by state and daylight-saving zone, so distinguish AEST from AEDT and account for state public holidays. Avoid inferring a visitor's location from an IP address when a declared state, postcode, or content relationship can provide a more reliable and privacy-conscious rule.
Test access, caching and edge cases
Test the finished block as different visitors. Check anonymous and authenticated sessions, each relevant role, service and non-service nodes, Sydney and Melbourne pages, Brisbane exclusions, unpublished content, search results, pagination, and 404 responses. Verify the block in every theme region it can occupy and on mobile viewports. A keyboard-only pass should confirm that the heading order, focus order, and link text remain clear when the block appears.
Caching problems often look like visibility failures. Clear Drupal's cache after changing a context, then test again with browser caching disabled. Confirm that the block declares suitable cache contexts and that any client-side script is attached through Drupal's asset system rather than loaded globally. Review permissions for block layout and context administration so only appropriate site builders can alter the rule. Finally, document the condition matrix in the site's change log; a concise note prevents a later content-model change from silently breaking the panel.
Performance checks belong in the same pass. Load a heavier service page with and without the panel, inspect the browser network panel, and confirm that no duplicate JavaScript or stylesheet is requested. The block should remain within the page's existing layout grid on narrow screens. A panel that appears correctly on a desktop template can otherwise push navigation or calls to action outside the viewport on a phone.
Compare the implementation choices
Choose the approach that matches how often the content and rules will change. Core blocks are quick for stable snippets, editorial modules suit reusable copy, custom plugins suit application-specific behaviour, and Layout Builder suits page-specific arrangements.
| Approach | Best suited to | Visibility model | Editorial control | Main limitation |
|---|---|---|---|---|
| Core block | Stable, simple page snippets | Core path, role and language conditions | Medium | Limited content-aware rules |
| Custom Block module with Context | Reusable copy managed by editors | Context conditions and reactions | High | Extra dependency and less custom behaviour |
| Custom block plugin with Context | Bespoke markup, forms or JavaScript | Context conditions and reactions | Low to medium | Requires development and maintenance |
| Layout Builder | Page-specific content arrangements | Layout restrictions and component visibility | High | Awkward as a reusable conditional block |
For this workflow, a custom block plugin plus a Context reaction gives precise placement without duplicating the block, while an editorial custom block offers a simpler path when non-developers own the copy. Export the context and module configuration in version control, and rehearse the deployment on a staging site so block IDs, regions, and content-type bundles match production.