Simple Block Plugins: Empty and Uppercasefor Drupal 11 , and 10

Last updated :  

Time to write our first block plugin — or rather, to read two that are already written, since that's how this course works. We're going to look at the two simplest possible block plugins in the block_example module: one that deliberately renders nothing, and one that renders a single line of text. Together they show you the absolute minimum shape a block plugin needs to take in Drupal 11.

What you'll learn in this lesson

  • The minimum PHP class structure every block plugin must follow
  • What the @Block annotation does, and why no other registration step is needed
  • What the build() method is responsible for, and what happens when it returns an empty array
  • Why every user-facing string in a block should be wrapped in $this->t()

The source files

Paths: modules/block_example/src/Plugin/Block/ExampleEmptyBlock.php and ExampleUppercaseBlock.php, in the same folder.

ExampleEmptyBlock.php

<?php

namespace Drupal\block_example\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Example: empty block' block.
 *
 * @Block(
 *   id = "example_empty",
 *   admin_label = @Translation("Example: empty block")
 * )
 */
class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   *
   * The return value of the build() method is a renderable array. Returning an
   * empty array will result in empty block contents. The front end will not
   * display empty blocks.
   */
  public function build() {
    // We return an empty array on purpose. The block will thus not be rendered
    // on the site. See BlockExampleTest::testBlockExampleBasic().
    return [];
  }

}

ExampleUppercaseBlock.php

<?php

namespace Drupal\block_example\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Example: uppercase this please' block.
 *
 * @Block(
 *   id = "example_uppercase",
 *   admin_label = @Translation("Example: uppercase this please")
 * )
 */
class ExampleUppercaseBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    return [
      '#markup' => $this->t("This block's title is changed to uppercase. Any block title which contains 'uppercase' will also be changed to uppercase."),
    ];
  }

}

How it works

PHP namespaces and autoloading

Both files open with:

namespace Drupal\block_example\Plugin\Block;

Drupal follows the PSR-4 autoloading standard, meaning the namespace directly mirrors the file's location on disk: src/Plugin/Block/ inside the module maps to Plugin\Block in the namespace. Because of this convention, Drupal's autoloader can find and load the right class file automatically — you'll never write a manual require or include statement for your own classes.

Extending BlockBase

use Drupal\Core\Block\BlockBase;
...
class ExampleEmptyBlock extends BlockBase {

BlockBase is an abstract class Drupal core provides specifically so you don't have to reimplement the plumbing every block needs. By extending it, your class automatically gets default implementations of blockAccess(), blockForm(), blockSubmit(), and blockValidate(), the t() translation helper, and label handling — all wired up for you. For these two simple blocks, the only method that needs overriding is build().

The @Block annotation

/**
 * @Block(
 *   id = "example_empty",
 *   admin_label = @Translation("Example: empty block")
 * )
 */

This docblock comment isn't just documentation — Drupal's plugin system actually parses it to register the class as a block plugin, with no YAML file or hook needed anywhere. Two keys matter here:

  • id — a unique machine name for this specific block plugin, used any time it's referenced in code or configuration.
  • admin_label — the human-readable name shown in the block-placement UI, wrapped in @Translation() so it can be localized.

Drupal discovers plugins like this automatically by scanning every enabled module's src/Plugin/Block/ directory — there's genuinely nothing else to register.

The build() method — two very different outputs

build() is the one method both classes override, and it's the heart of every block plugin: it must return a renderable array, Drupal's internal data structure for describing content before it becomes HTML.

public function build() {
    return [];
}

Returning a plain empty array tells Drupal, in no uncertain terms, "there is nothing to show here." Drupal's rendering pipeline checks for exactly this and, when it finds it, suppresses the block entirely — no wrapper <div>, no empty box, nothing in the page HTML at all, even if the block has been placed in a region. This is genuinely useful: a real-world block that conditionally has nothing to say (say, a "your cart" block for a user with an empty cart) can use this same pattern.

public function build() {
    return [
      '#markup' => $this->t("This block's title is changed to uppercase. Any block title which contains 'uppercase' will also be changed to uppercase."),
    ];
}

This one returns a render array with a single #markup key — the simplest way to output a string of text or safe HTML in Drupal's render system. Keys prefixed with # are always render array properties rather than nested content, and Drupal automatically runs #markup values through an XSS filter before output, so only a safe subset of HTML tags can slip through even if the string contained any.

$this->t() — translation from day one

$this->t() comes from StringTranslationTrait, included automatically via BlockBase. Wrapping a user-facing string in it registers that string with Drupal's translation system, making it available for translation at /admin/config/regional/translate without any further work on your part. The rule of thumb: any text a site visitor will read should go through $this->t() (inside a class) or the global t() function (in procedural code) — never a bare string.

Why does the block say "uppercase" and actually render in all caps? Look closely at the code above — it doesn't! The block's title gets uppercased by a separate hook implementation elsewhere in the module (a title-altering mechanism you'll recognize once you've done the Hooks topic). The block's own build() method only controls its body content, not its title — a distinction worth remembering.

See it for yourself

Go to Structure → Block layout → Place block (or directly to /admin/structure/block/add), place "Example: uppercase this please" into any region, and save. Visit the front page and confirm the block's title renders in capital letters.

The uppercase example block placed on the page, showing its title rendered in all capital letters

Key takeaways

  • Every block plugin in Drupal 11 is a PHP class extending BlockBase, living under src/Plugin/Block/, with a namespace that mirrors that path.
  • The @Block annotation with id and admin_label is all that's needed to register a block plugin — no YAML service definition, no hook.
  • build() must return a renderable array; returning an empty array [] causes Drupal to suppress the block entirely, producing no HTML output for it.
  • #markup is the simplest render array key for outputting text from a block, and it's automatically sanitized by Drupal's XSS filter.
  • $this->t() must wrap every user-facing string to make it translatable — it's available in any class extending BlockBase.

Coming up next

A block that always shows the same fixed text is useful, but real-world blocks usually need to be configurable by whoever places them — different text for different pages, without touching code. In the next lesson we'll add exactly that: a block with its own configuration form.