Skip to main content

Drupal website

Demo: Field formatters

Pick a field type and a formatter, change its settings, and watch the field update.

On the running site you can pick a formatter and change its settings. This copy of the site is static, so here are the formatters the demo starts with.

  1. 1. Stored value

    What the field saves in the database.

  2. 2. Formatter

    A plugin that turns the value into HTML, set up with its settings.

  3. 3. Output

    What visitors see on the page.

Plain text

string

Stored value

value
Drupal Camp Chattanooga
  • Drupal Camp Chattanooga

    Plain text

    string

    No settings

Formatted text

text_long

Stored value

value
Content Builder lets editors build a page from <strong>sections</strong> of blocks, right in the node form. Each section type decides its layout and the blocks it allows.
format
restricted_html
  • Content Builder lets editors build a page from sections of blocks, right in the node form. Each section type decides its layout and the blocks it allows.

    Default

    text_default

    No settings

  • Content Builder lets editors build a page from sections of blocks, right in the node form.

    Trimmed

    text_trimmed
    • Trimmed limit: 160 characters

Date

datetime

Stored value

value
2026-10-18T15:30:00
  • Sunday, October 18, 2026 at 3:30 pm

    Custom

    datetime_custom
    • PHP date format: l, F j, Y \a\t g:i a
  • Default

    datetime_default
    • Date format: Default medium date
  • 2026-10-18T15:30:00

    Plain

    datetime_plain

    No settings

Timestamp

timestamp

Stored value

value
1767225600
  • Default

    timestamp
    • Displayed as a time difference
    • Future date: 1 year 1 month hence
    • Past date: 1 year 1 month ago
    • Refresh every 1 minute
    • Disabled JavaScript format: medium
    • Tooltip date format: long

Number (integer)

integer

Stored value

value
1048576
  • 1 MB

    Bytes (KB, MB, ...)

    file_size

    No settings

  • 1,048,576

    Default

    number_integer
    • 1,234
    • Display with prefix and suffix.
  • 1048576

    Unformatted

    number_unformatted

    No settings

Number (decimal)

decimal

Stored value

value
1234.567
  • 1,234.57

    Default

    number_decimal
    • 1,234.12
    • Display with prefix and suffix.
  • 1234.567

    Unformatted

    number_unformatted

    No settings

Boolean

boolean

Stored value

value
true
  • Yes

    Boolean

    boolean
    • Display: Yes / No

Email

email

Stored value

value
hello@example.com
  • Email

    email_mailto

    No settings

  • hello@example.com

    Plain text

    basic_string

    No settings

How it’s built gwt_demo/src/Plugin/ProjectDemo/FieldFormatterDemo.php · 167 lines
<?php

declare(strict_types=1);

namespace Drupal\gwt_demo\Plugin\ProjectDemo;

use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\gwt_demo\Attribute\ProjectDemo;
use Drupal\gwt_demo\Form\FieldFormatterPlaygroundForm;
use Drupal\gwt_demo\ProjectDemo\FormatterPluginCollection;
use Drupal\gwt_demo\ProjectDemo\InteractiveProjectDemoInterface;
use Drupal\gwt_demo\ProjectDemo\PluginSystemDemoBase;
use Drupal\gwt_demo\Services\FormatterSamples;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Sample field values through field formatters.
 *
 * Visitors pick a sample field and a formatter, change its settings and see
 * the result (FieldFormatterPlaygroundForm). The demo's settings choose
 * formatters for each sample field and their settings, kept in a formatter
 * plugin collection under the "formatters" key: the playground starts from
 * them, and the static export shows them all as a gallery. The sample fields
 * are in Services\FormatterSamples.
 */
#[ProjectDemo(
  id: 'field_formatters',
  label: new TranslatableMarkup('Field formatters'),
  description: new TranslatableMarkup('Visitors try field formatters and their settings on sample field values. The static export shows the formatters chosen here.'),
)]
final class FieldFormatterDemo extends PluginSystemDemoBase implements InteractiveProjectDemoInterface {

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    #[Autowire(service: 'plugin.manager.field.formatter')]
    FormatterPluginManager $pluginManager,
    protected FormatterSamples $samples,
    protected FormBuilderInterface $formBuilder,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $pluginManager);
  }

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

  /**
   * {@inheritdoc}
   */
  protected function createPluginCollections(): array {
    return ['formatters' => new FormatterPluginCollection($this->pluginManager, $this->configuration['formatters'], $this->samples)];
  }

  /**
   * {@inheritdoc}
   */
  protected function isListed(array|PluginDefinitionInterface $definition, string $plugin_id): bool {
    return (bool) array_intersect($definition['field_types'], array_keys($this->samples->getFieldDefinitions()));
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
    $form['formatters'] = ['#tree' => TRUE];
    foreach ($this->samples->getFieldDefinitions() as $field_type => $field) {
      $form['formatters'][$field_type] = [
        '#type' => 'fieldset',
        '#title' => $field->getLabel(),
      ];
      foreach ($this->samples->getFormatterDefinitions($field) as $id => $definition) {
        $instance_id = $field_type . ':' . $id;
        $used = isset($this->configuration['formatters'][$instance_id]);
        $formatter = $this->getPlugin('formatters', $instance_id);
        if (!$formatter instanceof FormatterInterface) {
          $formatter = $this->samples->createFormatter($field, $id);
        }
        $form['formatters'][$field_type][$id] = [
          '#type' => 'details',
          '#title' => $definition['label'],
          '#open' => $used,
          'used' => [
            '#type' => 'checkbox',
            '#title' => $this->t('Show this formatter'),
            '#default_value' => $used,
          ],
          'settings' => array_diff_key($formatter->settingsForm([], $form_state), FormatterSamples::LOCKED_SETTINGS),
        ];
      }
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
    $formatters = [];
    foreach ($form_state->getValue('formatters') ?? [] as $field_type => $values) {
      foreach (array_filter($values, fn(array $value) => !empty($value['used'])) as $id => $value) {
        $formatters[$field_type . ':' . $id] = [
          'field' => $field_type,
          'type' => $id,
          'settings' => $value['settings'] ?? [],
        ];
      }
    }
    // The base class copies form values to settings of the same name; the
    // form groups formatters by field instead.
    parent::submitConfigurationForm($form, $form_state);
    $this->configuration['formatters'] = $formatters;
  }

  /**
   * {@inheritdoc}
   */
  public function build(): array {
    return $this->formBuilder->getForm(FieldFormatterPlaygroundForm::class, $this->configuration['formatters']);
  }

  /**
   * {@inheritdoc}
   *
   * Every sample field through every formatter chosen for it.
   */
  public function buildStatic(): array {
    $fields = [];
    foreach ($this->samples->getFieldDefinitions() as $field_type => $field) {
      $items = $this->samples->createItems($field);
      $shown = [];
      foreach ($this->samples->getFormatterDefinitions($field) as $id => $definition) {
        $formatter = $this->getPlugin('formatters', $field_type . ':' . $id);
        if ($formatter instanceof FormatterInterface) {
          $shown[] = [
            'label' => $definition['label'],
            'id' => $id,
            'summary' => $this->samples->summarize($formatter, $field),
            'output' => $this->samples->view($formatter, $items),
          ];
        }
      }
      if ($shown) {
        $fields[] = [
          'label' => $field->getLabel(),
          'type' => $field_type,
          'value' => $this->samples->getStoredValue($items),
          'formatters' => $shown,
        ];
      }
    }
    return [
      '#theme' => 'gwt_demo_field_formatters',
      '#fields' => $fields,
    ];
  }

}