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:
- Go to:
Structure > Content types > [Type] > Manage form display - Click settings for the name field
- 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:
- Navigate to:
/admin/structure/name - Click "Add name format"
- Enter:
- Label: "Last, First M.I."
- Machine name:
last_first_mi - Pattern:
f, g im. - 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:
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:
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:
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:
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¶
- Hooks Documentation - Available hooks
- Code Examples - Practical examples
- Services Documentation - Service APIs
- Classes Documentation - Class reference