Attaching Librariesfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Defining a library is like writing a recipe card — nobody actually eats until someone follows it in the kitchen. Attaching a library is that final step: telling Drupal "load this one, right here, on this specific page," using the #attached property on a render array. Get this part right and your CSS/JS loads exactly where it's needed and nowhere else — no wasted requests on pages that don't use it.

What you'll learn in this lesson

  • The standard way to attach a library via #attached['library']
  • How to pass PHP data into your JavaScript via drupalSettings, attached the same way
  • An alternative: attaching a library from inside a Twig template instead of PHP

Attaching a library, for real

Here's the Examples project's js_example module attaching its own colors library — the one you saw defined with dependencies in the last lesson — from a controller method:

public function showColors() {
  $colors = [
    'red' => $this->t('I am red.'),
    'blue' => $this->t('I am blue.'),
    'green' => $this->t('I am green.'),
    'cyan' => $this->t('I am cyan.'),
    'magenta' => $this->t('I am magenta.'),
    'yellow' => $this->t('I am yellow.'),
  ];

  // We give use an ID so that to target the HTML markup we added.
  $build['content'] = [
    '#markup' => '<div id="js-example-colors"></div>',
  ];

  // Attach the library.
  $build['#attached']['library'][] = 'js_example/colors';

  // Attach the JavaScript settings.
  $build['#attached']['drupalSettings']['javaScriptExample']['colors'] = $colors;

  return $build;
}

#attached['library'] — the standard route

$build['#attached']['library'][] = 'js_example/colors';

#attached is a special render array key: Drupal's renderer collects it from every element in the tree (however deeply nested) and merges it all into the final page's actual <head>/<script> output. Note the [] = — it's an array append, because a render array can attach any number of libraries, not just one.

Passing PHP data to JavaScript with drupalSettings

$build['#attached']['drupalSettings']['javaScriptExample']['colors'] = $colors;

This is the other half of the pattern: your PHP array of colors becomes real data your JavaScript can read at drupalSettings.javaScriptExample.colors once the page loads — no separate AJAX call needed just to hand over values you already computed server-side. It's attached exactly like a library, which is why core/drupalSettings had to be listed as a dependency in the previous lesson: without it, nothing reads this data back out on the client.

The alternative: attaching from Twig

The same module's accordion page does it differently — the library is attached inside the Twig template that renders the content, not in the PHP controller:

// The usual way to attach a library to a page is adding it to the render
// array via the #attached property. In this case, we added it via the
// template file we use. This is why the following lines are commented out.
//
// $build['accordion']['#attached']['library'][] = 'js_example/accordion';

A Twig template can call {{ attach_library('js_example/accordion') }} directly. Both approaches produce the identical result — the choice usually comes down to whether the theming layer or the PHP layer more naturally "owns" the decision of what to display and how.

Attach where the need is, not globally: it's tempting to attach a library your whole module uses inside hook_page_attachments() so it's "always available" — but that means every visitor downloads it on every page, even ones that never use it. Attach on the specific render array, controller, or template that actually needs it, and Drupal only ships the asset to pages that use it.

Quick check: you attach a library on a block plugin's render array, but the block never actually appears on a given page (say, it's configured to only show on the homepage). Does the library still load on other pages? (Answer: no — #attached only takes effect for elements that actually get rendered. A block that isn't shown contributes nothing to the final page's attachments, which is exactly why attaching at the render-array level, rather than globally, keeps unrelated pages lean.)

Key takeaways

  • $build['#attached']['library'][] = 'module_name/library_name'; is the standard way to load a library from PHP — always an array append, since multiple libraries can attach to one element.
  • #attached['drupalSettings'] passes PHP data to JavaScript as drupalSettings.yourKey — no separate AJAX round-trip required.
  • A Twig template can attach a library itself via {{ attach_library('module/library') }}, an alternative to doing it in PHP.
  • Attach libraries on the specific render array that needs them, not globally — unrendered elements contribute nothing, so scoping attachments correctly keeps unrelated pages fast.

Coming up next

You've now covered the full lifecycle of a front-end asset in Drupal: defining it, declaring what it depends on, and attaching it exactly where it's needed. Next, the focus shifts from performance and assets to security — starting with sanitizing user input, the first line of defense against XSS and injection in anything your module lets a visitor type.