How to Create a Custom Block in Drupal 9

By Arun AK, 10 September, 2021

Blocks are building blocks of Drupal website. Each content in a Drupal website belongs to one block or other. Blocks can be easily displayed in different part of the webpage using regions in themes.

There are two types of custom blocks in Drupal:

  1. Create using Drupal Graphical User Interface(GUI) system through /admin/structure/block/block-content
  2. Programmatically create based on your requirement

Let's see each method in more detail below.

Create a custom block using Drupal GUI

Creating a custom block using Drupal GUI is easy as in Drupal 9 all the custom content blocks are referred as 'Custom block'. You could create a new custom block by in the admin menu Structure >> Block layout >> Add custom block.

You can create different types of block as same as different type of nodes. Block types can be managed in Structure >> Block layout >> Block types. Blocks are also fieldable entities in Drupal 9. It is possible to add different types of fields to a bock type. Similar to content types, you could navigate to manage fields from Block types page.

Programmatically create a custom block

Blocks are plugins in Drupal. Custom plugin can be created using a custom module. So in this example, we name the module as ddocs_simple_block.

First create a folder under custom block directory of Drupal docroot. ie: docroot/modules/custom/ddocs_simple_block

Then create the info.yml file with module meta info that helps Drupal core to identify the custom module.

name: 'DDocs Simple Block'
type: module
description: 'DevelopersDocs simple block demo module.'
package: DDocs
core_version_requirement: ^8 || ^9

Create the custom plugin by extending the core BlockBase class. The new class file should place inside docroot/modules/custom/ddocs_simple_block/src/Plugin/Block directory.

<?php

namespace Drupal\ddocs_simple_block\Plugin\Block;

/**
 * Provides DDocs simple block.
 *
 * @Block(
 *   id = "ddocs_dimple_block",
 *   admin_label = @Translation("DDocs Simple Block"),
 *   category = @Translation("DDocs")
 * )
 */

class DdocsSimpleBlock extends BlockBase {

  public function build()
  {
    return [
      '#markup' => 'Welcome to DDocs!'
    ];
  }

}

If you want to cache the block per node and need to rebuild the block based on 'route' add following method to your class.

public function getCacheTags() {
    //With this when your node change your block will rebuild
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      //if there is node add its cachetag
      return Cache::mergeTags(parent::getCacheTags(), array('node:' . $node->id()));
    } else {
      //Return default tags instead.
      return parent::getCacheTags();
    }
  }

  public function getCacheContexts() {
    //if you depends on \Drupal::routeMatch()
    //you must set context of this block with 'route' context tag.
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }

Then custom block class finally looks like below:

<?php

namespace Drupal\ddocs_simple_block\Plugin\Block;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Block\BlockBase;

/**
 * Provides DDocs simple block.
 *
 * @Block(
 *   id = "ddocs_dimple_block",
 *   admin_label = @Translation("DDocs Simple Block"),
 *   category = @Translation("DDocs")
 * )
 */

class DdocsSimpleBlock extends BlockBase {

  public function build()
  {
    return [
      '#markup' => 'Welcome to DDocs!'
    ];
  }

  public function getCacheTags() {
    //With this when your node change your block will rebuild
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      //if there is node add its cachetag
      return Cache::mergeTags(parent::getCacheTags(), array('node:' . $node->id()));
    } else {
      //Return default tags instead.
      return parent::getCacheTags();
    }
  }

  public function getCacheContexts() {
    //if you depends on \Drupal::routeMatch()
    //you must set context of this block with 'route' context tag.
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }
}

A new block "DDocs Simple Block" now should be available under Block layout page, and we can assign it to the required region by using the Place block button next to each region name.

Hope this helps you to create a custom block programmatically based on your requirement. Cheers! :) ????