Custom block

Drupal custom block

Well, to create a custom block in Drupal is easy.

But, if you have to create a custom block I guess it should be more than just a simple text block.

Let's create a simple module to cover all steps.

There is a file tree for the simple module:

dw_block
├── src
│   └── Plugin
│       └── Block
│           └── FooBlock.php
└── dw_block.info.yml

File dw_block.info.yml

name: 'Foo Block'
type: module
description: 'This module provides example of custom block.'
core_version_requirement: ^9
package: 'Drupal Way'

File FooBlock.php

<?php

namespace Drupal\dw_block\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides "Foo Block" block.
 *
 * @Block(
 *   id = "dw_foo_block",
 *   admin_label = @Translation("Foo Block"),
 *   category = @Translation("Drupal Way")
 * )
 */
class FooBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build(): array {
    return [
      '#markup' => $this->t('This is Foo Block.'),
    ];
  }

}

So, this is it. If you don't need any logic here it looks very simple.

But, let's move on and add to this block a configuration form.

File FooBlock.php with the configuration form.

<?php

namespace Drupal\dw_block\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides "Foo Block" block.
 *
 * @Block(
 *   id = "dw_foo_block",
 *   admin_label = @Translation("Foo Block"),
 *   category = @Translation("Drupal Way")
 * )
 */
class FooBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build(): array {
    $config = $this->getConfiguration();

    return [
      '#markup' => $this->t('This is a title: @title', ['@title' => $config['title']]),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return ['title' => 'This is a title'];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state): array {
    $config = $this->getConfiguration();

    $form['title'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Title'),
      '#default_value' => $config['title'],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockValidate($form, FormStateInterface $form_state): void {
    if ($form_state->getValues()['title'] === 'No title') {
      $form_state->setErrorByName('title', $this->t('Please, enter something else.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state): void {
    $this->setConfiguration(['title' => $form_state->getValues()['title']]);
  }

}

Well, now it looks more complicated, but lest add something useful in custom code like DI (Dependency Injection)

File FooBlock.php with the configuration form and DI of entity type manager service.

<?php

namespace Drupal\dw_block\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides "Foo Block" block.
 *
 * @Block(
 *   id = "dw_foo_block",
 *   admin_label = @Translation("Foo Block"),
 *   category = @Translation("Drupal Way")
 * )
 */
class FooBlock extends BlockBase implements ContainerFactoryPluginInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  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'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function build(): array {
    $config = $this->getConfiguration();

    return [
      '#markup' => $this->t('This is a title: @title', ['@title' => $config['title']]),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return ['title' => 'This is a title'];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state): array {
    $config = $this->getConfiguration();

    $form['title'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Title'),
      '#default_value' => $config['title'],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockValidate($form, FormStateInterface $form_state): void {
    if ($form_state->getValues()['title'] === 'No title') {
      $form_state->setErrorByName('title', $this->t('Please, enter something else.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state): void {
    $this->setConfiguration(['title' => $form_state->getValues()['title']]);
  }

}

Also, I would like to add one note about DI in custom blocks — you have to implements "ContainerFactoryPluginInterface" interface for the block's class.

Actually, that all about custom blocks in Drupal.