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. Stored value
What the field saves in the database.
-
2. Formatter
A plugin that turns the value into HTML, set up with its settings.
-
3. Output
What visitors see on the page.
Plain text
string
Stored value
- value
- Drupal Camp Chattanooga
-
Drupal Camp Chattanooga
Plain text
stringNo 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_defaultNo settings
-
Content Builder lets editors build a page from sections of blocks, right in the node form.
Trimmed
text_trimmed- Trimmed limit: 160 characters
Link
link
Stored value
- uri
- https://www.drupal.org/project/content_builder
- title
- Content Builder on drupal.org
-
Link
link- Link text trimmed to 80 characters
- Add rel="nofollow"
- Open link in new window
-
Content Builder on drupal.org https://www.drupal.org/project/content_builder
Separate link text and URL
link_separate- Link text trimmed to 80 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_plainNo 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_sizeNo settings
-
1,048,576
Default
number_integer- 1,234
- Display with prefix and suffix.
-
1048576
Unformatted
number_unformattedNo 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_unformattedNo settings
Boolean
boolean
Stored value
- value
- true
-
Yes
Boolean
boolean- Display: Yes / No
email
Stored value
- value
- hello@example.com
-
Email
email_mailtoNo settings
-
hello@example.com
Plain text
basic_stringNo 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,
];
}
}