The Block Plugin System: Setting Up block_examplefor Drupal 11 , and 10

Last updated :  

You've now seen how Drupal recognizes a module (.info.yml) and how it discovers a menu link. In this lesson we're switching to a new example module — block_example — and a new topic entirely: Blocks. Blocks are the reusable, placeable chunks of content you see all over a Drupal site: the main navigation, a "Who's online" widget, a custom promo box in the sidebar. Over the next four lessons you'll learn exactly how they're built.

Before we can look at an actual block plugin, though, we need the same two pieces of scaffolding every module needs: an .info.yml file, and this time also a .routing.yml file that gives the module its own landing page. Think of this lesson as "setting the stage" — by the end of it you'll be able to visit the Block Example module's own page and confirm everything is wired up correctly before we write our first block.

What you'll learn in this lesson

  • How a .routing.yml file connects a URL to a PHP controller method
  • What each part of a route definition — path, controller, title, permission — actually does
  • How a module declares dependencies on other modules it needs (like Drupal core's own Block module)
  • Where to go in the admin UI to see the blocks this module will soon provide
New here? If you haven't done Lesson 1 (Module Structure) and Lesson 2 (Routing) yet, this lesson will make a lot more sense after those — we're reusing the exact same routing concepts on a brand new module.

The source file

Path: modules/block_example/block_example.routing.yml

block_example.description:
  path: '/examples/block-example'
  defaults:
    _controller: '\Drupal\block_example\Controller\BlockExampleController::description'
    _title: 'Block Example'
  requirements:
    _permission: 'access content'

How it works

The route name: block_example.description

The top-level YAML key is the machine name of the route — Drupal's internal identifier for it. You'll see this name used elsewhere in code, for example when generating a link with Url::fromRoute('block_example.description'). The naming convention is always module_name.route_identifier, which keeps every route in every module uniquely namespaced so two modules can never accidentally define the same route name.

path

path: '/examples/block-example'

This is the actual URL Drupal will match — visit /examples/block-example on your site and this route fires. The leading slash is required. Routes are registered into Drupal's routing table whenever the cache is rebuilt, so a brand-new route only becomes reachable after that happens (Drupal does this automatically when you enable a module).

defaults: _controller and _title

defaults:
    _controller: '\Drupal\block_example\Controller\BlockExampleController::description'
    _title: 'Block Example'

_controller is a fully-qualified reference to the PHP class and method that will actually handle the request — in the format \Drupal\{module}\Controller\{Class}::{method}. When someone visits the path above, Drupal instantiates BlockExampleController and calls its description() method, which returns a render array for the page content.

_title sets the page's <title> tag and heading. It's a static fallback — a controller can override it dynamically if it needs to, but for a simple informational page like this one, a fixed string is all that's needed.

requirements: gating access

requirements:
    _permission: 'access content'

Before Drupal ever calls your controller, it checks requirements. If they aren't met, the visitor gets a 403 Forbidden response and your controller code never runs at all. _permission is the simplest access rule: it names a Drupal permission the current user must hold. 'access content' is granted to nearly every role by default — including anonymous visitors — so this page is public. Other real-world routes might use something like 'administer site configuration' to restrict a route to administrators only.

The other half: block_example.info.yml

You already know most of this file from Lesson 1 — it's the same pattern, just for a different module:

name: Block Example
type: module
description: Demonstrates how a module can define blocks.
package: Example modules
core_version_requirement: ^10.3 || ^11.0
dependencies:
  - drupal:block
  - drupal:node
  - examples:examples

# Information added by Drupal.org packaging script on 2024-09-09
version: '4.0.4'
project: 'examples'
datestamp: 1725887893

The one thing worth pausing on here is the new dependency: drupal:block. This tells Drupal that before block_example can be enabled, the core Block module must already be enabled — because that's exactly where the plugin manager, the BlockBase class, and the block-placement admin UI all live. Every block plugin we write in the coming lessons will build directly on top of what that dependency guarantees is available.

See it for yourself

With block_example enabled, head to Structure → Block layout (/admin/structure/block) and click "Place block" on any region. Search for "Example" — you should see three block plugins available: Example: empty block, Example: uppercase this please, and Example: configurable text. We'll build and place each of these ourselves over the next three lessons.

The Block layout admin page listing the three Example block plugins available to place

Quick check: if you saw a 403 error visiting /examples/block-example as a logged-out visitor, which line in the YAML would you look at first? If you said the requirements section, you're already thinking like a Drupal developer.

Key takeaways

  • Every Drupal module requires an .info.yml file declaring its name, type, core_version_requirement, and dependencies — without it, Drupal won't recognize the module at all.
  • The dependencies array enforces hard prerequisites — listing drupal:block guarantees the Block plugin manager and BlockBase class are available before any of this module's block plugins load.
  • A .routing.yml file maps URL paths to PHP controller methods using a route_name: path / defaults / requirements structure, rebuilt whenever the cache clears.
  • The _controller value must be a fully-qualified \Drupal\{module}\Controller\{Class}::{method} string — this is how Drupal resolves and instantiates the right class.
  • requirements._permission is the simplest access control in Drupal routing: it gates the entire route behind one named permission, checked before your controller ever runs.

Coming up next

Scaffolding's done — now for the fun part. In the next lesson we'll open up the two simplest block plugins in this module and see exactly what a block plugin class looks like: the annotation that registers it, the one method it needs to override, and why an empty block quietly disappears from the page instead of rendering as a blank box.