Block API overview

Last updated on
5 April 2023

Overview

Blocks in Drupal 8 are actually made up of two separate API structures to create a user experience similar to what Drupal has maintained in past iterations. These two APIs are the Block Plugin API, which is a stand-alone reusable API, and the Block Entity API which is a Drupal 8-specific use case of block placement and visibility control.

Creating Block Plugin API blocks

Creating blocks defined in your module's code requires studying and understanding the Plugin API, and more specifically Annotations based plugin discovery, which is the mechanism that Drupal 8 uses to locate the code that defines your block.

Creating a custom block defined by your module involves the following steps:

Make your block visible to Drupal and your users

Drupal uses the PSR-4 standard for discovery. Assuming a module name of fax, the code for your custom block(s) should be placed into fax/src/Plugin/Block/. Each file in this directory should be named according to the class it contains. If we're going to define the class FaxBlock, it should be in a file named fax/src/Plugin/Block/FaxBlock.php  and have content along the lines of the following example:

namespace Drupal\fax\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Fax' block.
 *
 * @Block(
 *   id = "fax_block",
 *   admin_label = @Translation("Fax block"),
 * )
 */
class FaxBlock extends BlockBase {
  // Override BlockPluginInterface methods here.
}

The 'id' property in the annotation defines the unique, machine-readable ID of your block, and the name of the block as it will be seen by other code. The 'admin_label' annotation defines the human-readable name of the block that will be used when displaying your block in the administration interface. The available annotation properties can be found in \Drupal\Core\Block\Annotation\Block (the public properties).

The two most common methods to override are:

BlockPluginInterface::build() - which is expected to return a render array defining the content you want your block to display.

BlockBase::access() - which controls the block's visibility. It is expected to return an AccessResult object.

Add custom configuration options to your block

You can also add custom configuration options to the block configuration form by overriding the BlockPluginInterface::blockForm() and BlockPluginInterface::blockSubmit() methods and then using the BlockBase::setConfigurationValue() and BlockBase::getConfiguration().

In the next example we're adding a new textfield in our blockForm() method and then saving the user-submitted data in the blockSubmit() method. The code demonstrates how values might be retrieved when building the form, validated, and updated using the appropriate methods.

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;

/**
 * Provides a 'Fax' block.
 *
 * @Block(
 *   id = "fax_block",
 *   admin_label = @Translation("Fax block"),
 * )
 */
class FaxBlock extends BlockBase implements BlockPluginInterface {

  // Access method here ...

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

    $fax_number = isset($config['fax_number']) ? $config['fax_number'] : '';
    return [
      '#markup' => $this->t('The fax number is @number!', ['@number' => $fax_number]),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form = parent::blockForm($form, $form_state);

    // Retrieve existing configuration for this block.
    $config = $this->getConfiguration();

    // Add a form field to the existing block configuration form.
    $form['fax_number'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Fax number'),
      '#default_value' => isset($config['fax_number']) ? $config['fax_number'] : '',
    ];
    
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    // Save our custom settings when the form is submitted.
    $this->setConfigurationValue('fax_number', $form_state->getValue('fax_number'));
  }

  /**
   * {@inheritdoc}
   */
  public function blockValidate($form, FormStateInterface $form_state) {
    $fax_number = $form_state->getValue('fax_number');

    if (!is_numeric($fax_number)) {
      $form_state->setErrorByName('fax_number', t('Needs to be an integer'));
    }
  }
}

You could also use the BlockBase::getConfiguration() method in your build() method to retrieve the configuration data and display it to your users. The access() method of your block could also contain more complicated logic to determine whether the block should be displayed.

Example of access condition method.

 /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account, $return_as_object = FALSE) {
    return \Drupal\Core\Access\AccessResult::allowedIf($account->isAuthenticated());
  }

 
 

You could also create your own caching conditions

Example method using cache tags. Read more about cache tags.

 /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
     return \Drupal\Core\Cache\Cache::mergeTags(parent::getCacheTags(), ['node_list']);
  }

If you want to change the block cache max time to 0. Read more about cache max-age.

  public function getCacheMaxAge() {
    // If you want to disable caching for this block.
    return 0;
  }

Make your block translatable

Blocks are defined as plugins, which makes them configuration entities. In order for a configuration entity to be translatable, a schema representing the structure of the data needs to be provided in the module containing the block. Check here to see how to declare fields as translatable through their schema: https://www.drupal.org/docs/drupal-apis/configuration-api/configuration-.... Once your schema is defined and your cache rebuilt, you should be able to translate your block configuration fields.

So if we were to make the 'fax_number' field translatable: we would add a schema at fax/config/schema/fax.schema.yml with the following content:

block.settings.fax_block:
  type: block_settings
  label: 'Fax Block'
  mapping:
    fax_number:
      type: label
      label: 'Fax number'

Note that if we do not want the 'fax_number' field to be translatable, we could set the data type to string which is considered untranslatable.

Tags

Help improve this page

Page status: No known problems

You can: