Skip to content

Extending the Name Module

This guide covers how to extend and customize the Name module for specific needs.

Creating Custom Widget Layouts

Widget layouts control the visual arrangement of name components in forms. You can add custom layouts using hook_name_widget_layouts().

1. Implement the Hook

Create a custom module and implement the hook:

my_module.module:

/**
 * Implements hook_name_widget_layouts().
 */
function my_module_name_widget_layouts() {
  return [
    'compact_inline' => [
      'label' => t('Compact Inline'),
      'library' => ['my_module/name-compact-inline'],
      'wrapper_attributes' => [
        'class' => ['name-compact-inline', 'clearfix'],
      ],
    ],
    'two_column_grid' => [
      'label' => t('Two Column Grid'),
      'library' => ['my_module/name-grid-layout'],
      'wrapper_attributes' => [
        'class' => ['name-grid-2col'],
      ],
    ],
  ];
}

2. Define the Library

my_module.libraries.yml:

name-compact-inline:
  version: 1.0
  css:
    theme:
      css/name-compact-inline.css: {}
  dependencies:
    - core/drupal

name-grid-layout:
  version: 1.0
  css:
    theme:
      css/name-grid-layout.css: {}
  js:
    js/name-grid-layout.js: {}
  dependencies:
    - core/drupal
    - core/once

3. Create the Stylesheet

css/name-compact-inline.css:

.name-compact-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: flex-end;
}

.name-compact-inline .form-item {
  margin-bottom: 0;
  flex: 1 1 auto;
  min-width: 100px;
}

.name-compact-inline .form-item--title {
  flex: 0 0 80px;
}

.name-compact-inline .form-item--given,
.name-compact-inline .form-item--family {
  flex: 1 1 150px;
}

.name-compact-inline .form-item--middle {
  flex: 0 0 120px;
}

.name-compact-inline label {
  font-size: 0.875rem;
  margin-bottom: 0.25rem;
}

/* Responsive: stack on mobile */
@media (max-width: 768px) {
  .name-compact-inline {
    flex-direction: column;
  }

  .name-compact-inline .form-item {
    width: 100%;
  }
}

css/name-grid-layout.css:

.name-grid-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  column-gap: 2rem;
}

.name-grid-2col .form-item {
  margin-bottom: 0;
}

/* Title spans both columns */
.name-grid-2col .form-item--title {
  grid-column: 1 / -1;
}

/* Specific positioning */
.name-grid-2col .form-item--given {
  grid-column: 1;
  grid-row: 2;
}

.name-grid-2col .form-item--middle {
  grid-column: 2;
  grid-row: 2;
}

.name-grid-2col .form-item--family {
  grid-column: 1;
  grid-row: 3;
}

.name-grid-2col .form-item--credentials {
  grid-column: 2;
  grid-row: 3;
}

4. Using the Custom Layout

Once defined, the layout appears in widget settings:

  1. Go to: Structure > Content types > [Type] > Manage form display
  2. Click settings for the name field
  3. Select your custom layout from the "Layout" dropdown

Creating Custom Name Formats

Name formats define how name components are assembled for display.

1. Create Format via UI

The simplest way is through the admin interface:

  1. Navigate to: /admin/structure/name
  2. Click "Add name format"
  3. Enter:
  4. Label: "Last, First M.I."
  5. Machine name: last_first_mi
  6. Pattern: f, g im.
  7. Save

2. Create Format Programmatically

use Drupal\name\Entity\NameFormat;

// In hook_install() or update hook
function my_module_install() {
  // Create a custom format
  $formats = [
    'business_card' => [
      'label' => 'Business Card Format',
      'pattern' => 't g f, c',
    ],
    'academic' => [
      'label' => 'Academic Citation',
      'pattern' => 'f, ig. im.',
    ],
    'casual' => [
      'label' => 'Casual',
      'pattern' => 'g f',
    ],
  ];

  foreach ($formats as $id => $format_data) {
    if (!NameFormat::load($id)) {
      $format = NameFormat::create([
        'id' => $id,
        'label' => $format_data['label'],
        'pattern' => $format_data['pattern'],
        'locked' => FALSE,
      ]);
      $format->save();
    }
  }
}

Format Pattern Tokens

Available tokens for patterns:

Token Component Modifiers Example
t Title t = "Dr."
g Given name ig = "J", Ug = "JOHN"
m Middle name im = "R", Lm = "robert"
f Family name Uf = "SMITH", if = "S"
s Generational s = "Jr."
c Credentials c = "PhD"

Modifiers:

  • i - Initial only (e.g., ig = "J")
  • U - Uppercase (e.g., Uf = "SMITH")
  • L - Lowercase (e.g., Lg = "john")
  • F - Uppercase first letter (e.g., Fg = "John")
  • T - Title case (e.g., Tm = "Marie Ann")
  • S - Sentence case (e.g., Sg = "John")

Creating Custom List Formats

List formats control how multiple names are displayed together.

Programmatic Creation

use Drupal\name\Entity\NameListFormat;

function my_module_install() {
  $list_formats = [
    'authors' => [
      'label' => 'Authors List',
      'delimiter' => '; ',
      'and' => 'text',
      'delimiter_precedes_last' => 'contextual',
      'el_al_min' => 7,
      'el_al_first' => 6,
    ],
    'compact' => [
      'label' => 'Compact List',
      'delimiter' => ', ',
      'and' => 'symbol',
      'delimiter_precedes_last' => 'never',
      'el_al_min' => 4,
      'el_al_first' => 3,
    ],
  ];

  foreach ($list_formats as $id => $format_data) {
    if (!NameListFormat::load($id)) {
      $format = NameListFormat::create([
        'id' => $id,
        'label' => $format_data['label'],
        'delimiter' => $format_data['delimiter'],
        'and' => $format_data['and'],
        'delimiter_precedes_last' => $format_data['delimiter_precedes_last'],
        'el_al_min' => $format_data['el_al_min'],
        'el_al_first' => $format_data['el_al_first'],
      ]);
      $format->save();
    }
  }
}

List Format Settings

delimiter: Separator between names - Default: ", " - Examples: ", ", "; ", " | "

and: Conjunction type for last name - 'text' - "and" - 'symbol' - "&" - 'inherit' - Use delimiter

delimiter_precedes_last: Delimiter before conjunction - 'never' - "A, B and C" - 'always' - "A, B, and C" - 'contextual' - Depends on count

el_al_min: Minimum names before using "et al" - 0 - Disabled - 5 - Use "et al" if more than 5 names

el_al_first: How many to show before "et al" - Must be less than el_al_min

Creating Custom Field Widgets

Create a custom widget plugin to change how name fields are edited.

1. Create the Widget Plugin

src/Plugin/Field/FieldWidget/SimplifiedNameWidget.php:

<?php

namespace Drupal\my_module\Plugin\Field\FieldWidget;

use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Plugin implementation of the 'simplified_name' widget.
 */
#[FieldWidget(
  id: 'simplified_name',
  label: new TranslatableMarkup('Simplified Name'),
  field_types: [
    'name',
  ],
)]
class SimplifiedNameWidget extends WidgetBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'show_title' => TRUE,
      'placeholder_given' => 'First name',
      'placeholder_family' => 'Last name',
    ] + parent::defaultSettings();
  }

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

    $elements['show_title'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show title field'),
      '#default_value' => $this->getSetting('show_title'),
    ];

    $elements['placeholder_given'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Placeholder for given name'),
      '#default_value' => $this->getSetting('placeholder_given'),
    ];

    $elements['placeholder_family'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Placeholder for family name'),
      '#default_value' => $this->getSetting('placeholder_family'),
    ];

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = [];

    if ($this->getSetting('show_title')) {
      $summary[] = $this->t('With title field');
    }
    else {
      $summary[] = $this->t('Without title field');
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function formElement(
    FieldItemListInterface $items,
    $delta,
    array $element,
    array &$form,
    FormStateInterface $form_state
  ) {
    $item = $items[$delta];

    $element['#type'] = 'fieldset';
    $element['#attributes']['class'][] = 'simplified-name-widget';

    if ($this->getSetting('show_title')) {
      $element['title'] = [
        '#type' => 'select',
        '#title' => $this->t('Title'),
        '#options' => [
          '' => $this->t('- None -'),
          'Mr.' => $this->t('Mr.'),
          'Mrs.' => $this->t('Mrs.'),
          'Ms.' => $this->t('Ms.'),
          'Dr.' => $this->t('Dr.'),
        ],
        '#default_value' => $item->title ?? '',
      ];
    }

    $element['given'] = [
      '#type' => 'textfield',
      '#title' => $this->t('First Name'),
      '#default_value' => $item->given ?? '',
      '#placeholder' => $this->getSetting('placeholder_given'),
      '#required' => $element['#required'],
    ];

    $element['family'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Last Name'),
      '#default_value' => $item->family ?? '',
      '#placeholder' => $this->getSetting('placeholder_family'),
      '#required' => $element['#required'],
    ];

    // Hidden fields for unused components
    foreach (['middle', 'generational', 'credentials'] as $component) {
      $element[$component] = [
        '#type' => 'value',
        '#value' => '',
      ];
    }

    return $element;
  }
}

2. Add Widget Styling

my_module.libraries.yml:

simplified_name_widget:
  version: 1.0
  css:
    theme:
      css/simplified-name-widget.css: {}

css/simplified-name-widget.css:

.simplified-name-widget {
  display: flex;
  gap: 1rem;
  border: none;
  padding: 0;
  margin: 0;
}

.simplified-name-widget > .form-item {
  flex: 1;
  margin-bottom: 0;
}

.simplified-name-widget > .form-item--title {
  flex: 0 0 120px;
}

3. Attach the Library

In your widget's formElement() method:

$element['#attached']['library'][] = 'my_module/simplified_name_widget';

Creating Custom Field Formatters

Create a custom formatter to change how name fields are displayed.

1. Create the Formatter Plugin

src/Plugin/Field/FieldFormatter/BadgeNameFormatter.php:

<?php

namespace Drupal\my_module\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Plugin implementation of the 'badge_name' formatter.
 */
#[FieldFormatter(
  id: 'badge_name',
  label: new TranslatableMarkup('Badge Name'),
  field_types: [
    'name',
  ],
)]
class BadgeNameFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'format' => 'given_family',
      'show_title' => FALSE,
      'uppercase' => TRUE,
    ] + parent::defaultSettings();
  }

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

    $elements['format'] = [
      '#type' => 'select',
      '#title' => $this->t('Name format'),
      '#options' => [
        'given_family' => $this->t('First Last'),
        'family_given' => $this->t('Last, First'),
        'given_only' => $this->t('First name only'),
      ],
      '#default_value' => $this->getSetting('format'),
    ];

    $elements['show_title'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show title'),
      '#default_value' => $this->getSetting('show_title'),
    ];

    $elements['uppercase'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Uppercase'),
      '#default_value' => $this->getSetting('uppercase'),
    ];

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = [];

    $formats = [
      'given_family' => $this->t('First Last'),
      'family_given' => $this->t('Last, First'),
      'given_only' => $this->t('First name only'),
    ];

    $summary[] = $this->t('Format: @format', [
      '@format' => $formats[$this->getSetting('format')],
    ]);

    if ($this->getSetting('show_title')) {
      $summary[] = $this->t('With title');
    }

    if ($this->getSetting('uppercase')) {
      $summary[] = $this->t('Uppercase');
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    foreach ($items as $delta => $item) {
      $name_parts = [];

      if ($this->getSetting('show_title') && !empty($item->title)) {
        $name_parts[] = $item->title;
      }

      switch ($this->getSetting('format')) {
        case 'given_family':
          if (!empty($item->given)) {
            $name_parts[] = $item->given;
          }
          if (!empty($item->family)) {
            $name_parts[] = $item->family;
          }
          break;

        case 'family_given':
          if (!empty($item->family)) {
            $name_parts[] = $item->family . ',';
          }
          if (!empty($item->given)) {
            $name_parts[] = $item->given;
          }
          break;

        case 'given_only':
          if (!empty($item->given)) {
            $name_parts[] = $item->given;
          }
          break;
      }

      $name = implode(' ', $name_parts);

      if ($this->getSetting('uppercase')) {
        $name = mb_strtoupper($name);
      }

      $elements[$delta] = [
        '#type' => 'html_tag',
        '#tag' => 'div',
        '#value' => $name,
        '#attributes' => [
          'class' => ['badge-name'],
        ],
        '#attached' => [
          'library' => ['my_module/badge_name_formatter'],
        ],
      ];
    }

    return $elements;
  }
}

2. Add Formatter Styling

my_module.libraries.yml:

badge_name_formatter:
  version: 1.0
  css:
    theme:
      css/badge-name-formatter.css: {}

css/badge-name-formatter.css:

.badge-name {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  font-weight: bold;
  border-radius: 4px;
  font-size: 1.125rem;
}

Extending the Name Formatter Service

You can create a decorator service to add custom formatting logic.

1. Create a Custom Formatter

src/CustomNameFormatter.php:

<?php

namespace Drupal\my_module;

use Drupal\name\NameFormatterInterface;

/**
 * Custom name formatter decorator.
 */
class CustomNameFormatter implements NameFormatterInterface {

  /**
   * The wrapped name formatter.
   */
  protected $nameFormatter;

  /**
   * Constructor.
   */
  public function __construct(NameFormatterInterface $name_formatter) {
    $this->nameFormatter = $name_formatter;
  }

  /**
   * {@inheritdoc}
   */
  public function format(array $components, $type = 'default', $langcode = NULL) {
    // Add custom logic before formatting
    $components = $this->preprocessComponents($components);

    return $this->nameFormatter->format($components, $type, $langcode);
  }

  /**
   * {@inheritdoc}
   */
  public function formatList(array $items, $type = 'default', $list_type = 'default', $langcode = NULL) {
    return $this->nameFormatter->formatList($items, $type, $list_type, $langcode);
  }

  /**
   * Custom preprocessing logic.
   */
  protected function preprocessComponents(array $components) {
    // Example: Auto-capitalize given and family names
    if (isset($components['given'])) {
      $components['given'] = ucfirst(mb_strtolower($components['given']));
    }
    if (isset($components['family'])) {
      $components['family'] = ucfirst(mb_strtolower($components['family']));
    }

    return $components;
  }

  // Implement other interface methods by delegating to wrapped formatter
  public function setSetting($key, $value) {
    return $this->nameFormatter->setSetting($key, $value);
  }

  public function getSetting($key) {
    return $this->nameFormatter->getSetting($key);
  }

  public function getLastDelimiterTypes($include_examples = TRUE) {
    return $this->nameFormatter->getLastDelimiterTypes($include_examples);
  }

  public function getLastDelimiterBehaviors($include_examples = TRUE) {
    return $this->nameFormatter->getLastDelimiterBehaviors($include_examples);
  }
}

2. Register the Service Decorator

my_module.services.yml:

services:
  my_module.custom_name_formatter:
    class: Drupal\my_module\CustomNameFormatter
    decorates: name.formatter
    arguments: ['@my_module.custom_name_formatter.inner']
    public: true

Adding Component Options from Taxonomy

You can populate component options from taxonomy vocabularies.

1. Create a Vocabulary

Create a vocabulary for titles, e.g., "Professional Titles": - Machine name: professional_titles - Add terms: "Associate Professor", "Assistant Professor", "Professor Emeritus"

2. Configure Field to Use Vocabulary

In field settings for title_options, add:

[vocabulary:professional_titles]
Dr.
Mr.
Mrs.

The field will now include both the direct options and all terms from the vocabulary.

3. Programmatic Configuration

$field = FieldConfig::loadByName('node', 'person', 'field_full_name');
$settings = $field->getSettings();

$settings['title_options'][] = '[vocabulary:professional_titles]';

$field->setSettings($settings);
$field->save();

See Also