08.10.2026 | Michael Ebert

Filter user entity reference field options to active users only

Option 1 (recommended): custom entity reference selection handler. Further options down below.

ActiveUserSelection.php
<?php

namespace Drupal\my_module\Plugin\EntityReferenceSelection;

use Drupal\Core\Entity\Plugin\EntityReferenceSelection\UserSelction;
use Drupal\Core\StringTranslation\TranslatableMarkup;

#[EntityReferenceSelection(
  id: "default:active_user",
  label: new TranslatableMarkup("User selection (only active users)"),
  entity_types: ["user"],
  group: "default",
  weight: 1
)]
class ActiveUserSelection extends UserSelection {

  /**
   * {@inheritdoc}
   */
  protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
    $query = parent::buildEntityQuery($match, $match_operator);
    $query->condition('status', 1);

    return $query;
  }

}

After implementing that plugin, you need to activate it on the entity form display for the field you want it.

Issue:

In a Drupal entity reference field configured to reference user accounts, the select list currently includes all users, including blocked or deactivated accounts.

Expected behavior: only active users should be available for selection by default, or there should be a configurable option to exclude inactive accounts from the reference field results.

Solution:

See the three examples described above.

Option 1 (custom Selection handler, see above)

The best general-purpose solution. It uses the exact extension point Drupal designed for this problem, is scoped precisely to the fields where you assign it, is trivially unit/kernel-testable, and doesn't rely on fragile internals like query tags and metadata that can silently break across core updates.

Option 2: Query tag alter

Avoid this unless you have a very narrow, temporary need and full control over exactly which query is being altered. It's the least robust of the three and the easiest to get wrong (over-broad conditions affecting unrelated queries, or under-broad checks that silently stop working after a core update).

my_module.module
/**
 * Implements hook_entity_query_tag__TAG_alter().
 */
function my_module_entity_query_tag__entity_reference_alter(QueryInterface $query) {
  $handler = $query->getMetadata('entity_reference_selection_handler');

  if (!$handler) {
    return;
  }

  $configuration = $handler->getConfiguration();

  if (($configuration['target_type'] ?? NULL) !== 'user') {
    return;
  }

  // Filter for active users only.
  $query->condition('status', 1);
}

Option 3: entity reference view display

  1. Edit your Entity Reference field and select "Views: Filter by an entity reference view" as the reference method.
  2. Create or select a View that displays only the entities you want to show. Configure filters, sorting, and fields as needed.

The View's filters automatically control which options appear in the dropdown. No custom code required – it's all configured through the UI.

This can be chosen if (sitebuilders/editors need to have permissions to edit the view and maybe there is a config_ignore for that to prevent overrides during deployments):

  • Site builders/editors need to tweak the filtering criteria without a deployment (e.g., "also exclude role X" added later via UI), or
  • You need additional Views features (exposed filters, sorting by last login, relationships to other entities) in the same selection.

Weitere DevBits

05.10.2026 | Lothar Ferreira Neumann

Replace Tagify CDN-loaded contrib library with a local npm asset in Drupal

install_libraries_locally.sh
composer require npm-asset/yaireo--tagify:4.35.4
my_module.module
/**
 * Implements hook_library_info_alter().
 *
 * The tagify module loads Tagify from cdnjs.cloudflare.com. Serve it from
 * npm-asset/yaireo--tagify in /libraries instead, so no request reaches a
 * third party and the widget keeps working behind restrictive firewalls.
 */
function my_module_library_info_alter(array &$libraries, string $extension): void {
  if ($extension !== 'tagify') {
    return;
  }

  // Assigning replaces the CDN entries. The npm package ships an unminified
  // UMD build and no *.min.css; Drupal aggregates them either way.
  $libraries['tagify']['js'] = [
    '/libraries/yaireo--tagify/dist/tagify.js' => ['minified' => FALSE],
  ];
  $libraries['tagify']['css']['component'] = [
    '/libraries/yaireo--tagify/dist/tagify.css' => ['minified' => FALSE],
  ];
  $libraries['tagify_polyfils']['js'] = [
    '/libraries/yaireo--tagify/dist/tagify.polyfills.min.js' => ['minified' => TRUE],
  ];

  unset($libraries['tagify']['remote'], $libraries['tagify_polyfils']['remote']);
}
16.07.2026 | Lothar Ferreira Neumann

How to translate the Maxlength Countdown message label globally

A Drupal backend screenshot showing the "Count down message" field

Leave this field empty to allow for translation via Drupal interface translation.


Example configuration created by enabling Maxlength:

core.entity_form_display.node.article_not_translatable.default.yml
# ...
content:
  field_copyright:
    type: text_textarea
    weight: 3
    region: content
    settings:
      rows: 2
      placeholder: ''
    third_party_settings:
      allowed_formats:
        hide_help: '1'
        hide_guidelines: '1'
      maxlength:
        maxlength_js: null
        maxlength_js_label: 'Content limited to @limit characters, remaining: <strong>@remaining</strong>'
        maxlength_js_enforce: false
# ...

However, the module already contains fallback logic for this label in maxlength.module: 

maxlength.module
$maxlength_js_label = !empty($thirdPartySettings['maxlength']['maxlength_js_label']) ? $thirdPartySettings['maxlength']['maxlength_js_label'] : t('Content limited to @limit characters, remaining: <strong>@remaining</strong>');
$maxlength_js = $thirdPartySettings['maxlength']['maxlength_js'];

Working configuration: empty the label

core.entity_form_display.node.article_translatable.default.yml
# ...
content:
  field_copyright:
    type: text_textarea
    weight: 3
    region: content
    settings:
      rows: 2
      placeholder: ''
    third_party_settings:
      allowed_formats:
        hide_help: '1'
        hide_guidelines: '1'
      maxlength:
        maxlength_js: null
        maxlength_js_label: ''
        maxlength_js_enforce: false
# ...

With the label empty, the module falls back to its internal t() string, which can then be translated normally via the interface translation system.