Skip to content

Once API

When to Use

Every time you process elements in a behavior. The once() API prevents duplicate initialization and is required for proper AJAX compatibility.

Decision

The once() API marks elements as processed using a data attribute, ensuring code runs only once per element even when behaviors re-execute. As of Drupal 10, once() is vanilla JavaScript (removed jQuery.once dependency).

Critical change in Drupal 10: jQuery.once() removed, replaced with @drupal/once npm package. Must use core/once dependency and once() function.

Pattern

Standard once() pattern:

// Returns array of unprocessed elements
once('unique-identifier', '.selector', context).forEach(function (element) {
  // This code runs exactly once per element
  // even if behavior runs multiple times
});

Multiple selectors:

once('tabs-init', '.tab, .accordion, .toggle', context).forEach(function (element) {
  // Handles multiple selector types with one once ID
});

Removing once tracking (rare, for dynamic updates):

// Mark element as unprocessed (removes data attribute)
// Use in detach() when element will be reprocessed
once.remove('unique-id', '.selector', context);

Checking if already processed:

// Use once.find() to get already-processed elements
const processedElements = once.find('unique-id', context);

How it works: once() adds a single data-once attribute whose value is a space-separated list of ids, and skips elements already carrying the id in subsequent runs. There is no per-id attribute — a selector written against one never matches.

Migration reference: https://drupalbook.org/blog/replace-jqueryonce-javascript-once-drupal-10

Common Mistakes

  • Using jQuery.once() - WHY: Removed in Drupal 10, code breaks
  • Same once ID across different purposes - WHY: Elements get skipped incorrectly, mysterious bugs
  • Forgetting context parameter - WHY: Processes entire document, breaks AJAX, performance penalty
  • Processing without once() - WHY: Code runs multiple times, duplicate event bindings, memory leaks, broken functionality
  • Generic once IDs like 'init' - WHY: Conflicts with other modules using same ID

See Also