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.