A well-defined library (from the last lesson) is only half the win — if you attach it to every page regardless of whether that page needs it, you're back to shipping unused bytes everywhere. #attached is Drupal's mechanism for saying "load this library, but only on the render array that actually uses it" — and getting that scoping right is what separates a library that's merely well-organized from one that's genuinely efficient.
What you'll learn in this lesson
- How the
#attachedrender array key ties a library to one specific piece of output, not the whole page - The difference between attaching to a controller's top-level render array vs. one specific element inside it
- How to pass PHP data to JavaScript safely via
#attached['drupalSettings'] - Why
hook_page_attachments()is the wrong tool for "only on some pages" and what to use instead
Attaching a library to a real render array
Here's a real controller method from Drupal's Examples module attaching the colors library alongside data for it to consume:
$build['#attached']['library'][] = 'js_example/colors';
$build['#attached']['drupalSettings']['javaScriptExample']['colors'] = $colors;
Two things happen here. First, library is an indexed array — using [] = appends rather than overwrites, so multiple libraries can be attached to the same render array without clobbering each other. Second, drupalSettings passes real PHP data (here, a $colors array built earlier in the method) into JavaScript's global drupalSettings object, which the attached JS file can then read at drupalSettings.javaScriptExample.colors.
Top-level vs. nested attachment
Attaching to $build['#attached'] (the top level of the array you return) scopes the library to the whole page render. But if your render array has multiple nested elements and only one of them actually needs the library — say, one block inside a page with several — attach it to that specific nested element instead:
$build['accordion']['#attached']['library'][] = 'js_example/accordion';
This matters because Drupal's render pipeline only includes an element's #attached assets in the final page if that element actually gets rendered. A library attached deep inside a render array that gets conditionally skipped never loads at all — which is exactly the fine-grained control you want, and exactly why bolting a library onto the outermost array "to be safe" throws that control away.
hook_page_attachments() to conditionally load a library based on the current route. Resist it for anything beyond truly global, every-page assets (like a site-wide analytics snippet) — hook_page_attachments() runs on every single request, and stuffing route-checking logic into it just to decide "should I attach this" adds overhead to every page load, including the ones that don't need the library. Attaching directly inside the controller or block plugin that builds the relevant render array is both simpler and inherently scoped to only the requests that matter.Reading the attached data back in JavaScript
On the JS side, drupalSettings data attached this way is available the moment your library's JS file runs — no AJAX round-trip needed to fetch configuration that the server already had on hand while rendering the page. This is the standard pattern for "PHP computed a value, JS needs to know it" — far more efficient than having the JS make a second request back to the server just to ask for the same data.
Quick check: a page has three blocks, and only one of them uses a particular JavaScript widget. Where should that widget's library be attached — the page template, or the specific block's own render array? (If you said the block's own render array, you've got it — attaching at the page level would load the widget's JS on every page that includes the template, even pages where that specific block isn't placed.)
Key takeaways
#attached['library'][]uses append syntax ([] =) so multiple libraries can be attached to the same render array without overwriting each other.- Attach a library as deep in the render array as you can — to the specific element that needs it, not the outermost array — so it only loads when that element actually renders.
- Use
#attached['drupalSettings']to hand PHP-computed data to JavaScript without a separate AJAX request. - Avoid
hook_page_attachments()for route-conditional loading — it runs on every request regardless; attach inside the specific controller/block/form that needs the asset instead.
Coming up next
You've now got two ends of the same problem solved: minimal libraries, attached only where needed. Next, we widen the lens from your own module's assets to how libraries relate to each other at scale — managing dependencies across many libraries without creating a tangled, hard-to-reason-about load order.