Auto-creating Drupal user entities with custom entity reference selection plugin

As Drupal developers, we often work with entity reference fields that need to reference users. One not-so-common but sometimes necessary requirement is allowing content editors to create new users on the fly when entering content. While Drupal's entity reference fields provide an auto_create option (Create referenced entities if they don't already exist), it falls short when working with user entities because the label alone isn't sufficient to create a valid user account.

The default behavior of entity reference auto_create works well for simple content entities where a label is all you need, such as taxonomy terms. However, user entities require additional mandatory fields like username, email, and password. Simply enabling auto_create on a user reference field won't work because Drupal doesn't know how to generate these required values.

If we try to enter a user name that doesn't exist into the entity reference field, like in this case:

Image

as soon as we hit the Save button, we'll get an error like this:

Image

To solve this, we can create a custom Entity Reference Selection plugin that extends Drupal's default user selection handler. This plugin will handle the automatic creation of user accounts with proper username, email, and password generation.

Here's the implementation:

<?php

namespace Drupal\MY_MODULE\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\user\Plugin\EntityReferenceSelection\UserSelection;
use Drupal\user\UserInterface;

/**
 * Improved user selection implementation of Entity Reference Selection plugin.
 */
#[EntityReferenceSelection(
  id: "default:improved_user",
  label: new TranslatableMarkup("Improved user selection"),
  group: "default",
  weight: 1,
  entity_types: ["user"]
)]
class ImprovedUserSelection extends UserSelection {

  /**
   * {@inheritdoc}
   */
  public function createNewEntity($entity_type_id, $bundle, $label, $uid): EntityInterface|UserInterface {
    $username = $this->generateUsername($label);
    $email = $this->generateEmail($username);
    $password = $this->generatePassword($username);
    /** @var \Drupal\user\UserInterface $user */
    $user = $this->entityTypeManager->getStorage('user')->create([
      'name' => $username,
      'mail' => $email,
      'pass' => $password,
      'field_full_name' => $label,
    ]);
    $user->activate();
    return $user;
  }
  
  /**
   * Generates a valid username from a label.
   */
  protected function generateUsername(string $label): string {
    $username = trim($label);
    $username = mb_strtolower($username);
    return str_replace(' ', '.', $username);
  }
  
  /**
   * Generates an email address from a username.
   */
  protected function generateEmail(string $username): string {
    return $username . '@example.com';
  }
  
  /**
   * Generates a password from a username.
   */
  protected function generatePassword(string $username): string {
    return $username . '#super_strong_password_suffix';
  }

}

The plugin does several key things:

  • It extends Drupal's default UserSelection handler, keeping all the existing functionality for user reference fields.
  • It overrides the createNewEntity() method to handle user creation with all required fields.
  • It provides helper methods to generate a username, an email address and a password.
  • It automatically activates the account.

Applying the selection handler to existing fields

If you've created your entity reference field through Drupal's UI, the custom selection handler won't be available in the interface. You'll need to update the field configuration programmatically using an update hook.

Add this to your module's .install file:

/**
 * Updates user reference field to use improved selection handler.
 */
function MY_MODULE_update_10001() {
  $field_config = \Drupal::configFactory()->getEditable('field.field.node.my_content_type.field_customer');
  if ($field_config) {
    $field_config->set('settings.handler', 'default:improved_user');
    $field_config->set('settings.handler_settings.auto_create', TRUE);
    $field_config->save(TRUE);
  }
}

Field definition example

Here's a complete example of how to implement this in your code using BaseFieldDefinition:

$fields['customer'] = BaseFieldDefinition::create('entity_reference')
  ->setLabel(t('Customer'))
  ->setDescription(t('The customer associated with this order.'))
  ->setRequired(TRUE)
  ->setSetting('target_type', 'user')
  ->setSetting('handler', 'default:improved_user')
  ->setSetting('handler_settings', [
    'include_anonymous' => FALSE,
    'filter' => [
      'role' => ['customer'],
    ],
    'auto_create' => TRUE,
    'auto_create_bundle' => 'user',
  ])
  ->setDisplayOptions('view', [
    'label' => 'above',
    'type' => 'entity_reference_label',
    'weight' => -4,
  ])
  ->setDisplayOptions('form', [
    'type' => 'entity_reference_autocomplete',
    'weight' => -4,
    'settings' => [
      'match_operator' => 'CONTAINS',
      'size' => 60,
      'placeholder' => 'Start typing to search customers...',
    ],
  ])
  ->setDisplayConfigurable('form', TRUE)
  ->setDisplayConfigurable('view', TRUE);

This solution provides a seamless way to create user accounts through entity reference fields while maintaining proper user entity requirements. It's particularly useful in scenarios where content editors need to reference new users without leaving the content creation form. 

Remember to adapt the email domain, and password generation to match your site's requirements. You might also want to add more sophisticated username generation logic to handle special characters and ensure uniqueness.

User experience considerations are also important. Consider implementing welcome emails and verification processes for new accounts. This not only improves security but also helps users understand that an account has been created for them. You might want to set temporary passwords and force users to change them on first login, rather than generating permanent passwords programmatically.

About the Author

Goran Nikolovski is a web and AI developer with over 10 years of expertise in PHP, Drupal, Python, JavaScript, React, and React Native. He founded this website and enjoys sharing his knowledge.

AI Assistant