Skip to content

Drupal.behaviors Pattern

When to Use

Always for DOM manipulation in Drupal. Behaviors are the foundation of Drupal JavaScript - they work with AJAX, BigPipe, and dynamic content loading.

Decision

Drupal.behaviors is an object where each property is a behavior. Behaviors have attach() (required) and detach() (optional) methods. Drupal automatically executes all behaviors on page load and whenever AJAX updates the DOM.

WHY behaviors exist: jQuery's $(document).ready() only runs once on initial page load. Drupal's AJAX, BigPipe, and dynamic content require JavaScript to initialize new content after page load. Behaviors solve this by re-running whenever the DOM changes.

Pattern

Standard behavior structure:

(function (Drupal, once) {
  'use strict';

  Drupal.behaviors.moduleBehavior = {
    attach(context, settings) {
      once('unique-id', '.selector', context).forEach(function (element) {
        // Initialize element
        element.addEventListener('click', function() {
          // Event handling
        });
      });
    },

    detach(context, settings, trigger) {
      if (trigger === 'unload') {
        // Cleanup: remove event listeners, clear intervals
        // Prevents memory leaks
      }
    }
  };
})(Drupal, once);

Global operation pattern (runs once per page):

once('global-init', 'html').forEach(function () {
  // Page-level initialization
  // Equivalent to $(document).ready()
});

Common Mistakes

  • Wrong: Using $(document).ready() → Right: Use Drupal.behaviors
  • Why: Only runs once, breaks with AJAX/BigPipe
  • Wrong: Not using context parameter → Right: Always query within context
  • Why: Scans entire DOM every time, severe performance penalty
  • Wrong: Missing once() wrapper → Right: Wrap element processing with once()
  • Why: Code runs multiple times on same element, duplicate event bindings, memory leaks
  • Wrong: No detach() implementation → Right: Clean up in detach() when using intervals/global listeners
  • Why: Memory leaks from event listeners and intervals that never clean up
  • Wrong: jQuery dependency in IIFE without declaring it → Right: Declare all IIFE parameters
  • Why: Code breaks if jQuery loads after your script

See Also