Add CSS and JavaScript in Drupal module
This documentation is for modules. For information about themes, see Adding stylesheets (CSS) and JavaScript (JS) to a Drupal 8 theme.
In Drupal 8, stylesheets (CSS) and JavaScript (JS) are loaded using the same system for both modules (code) and themes: the asset library. Asset libraries can include one or more CSS resources, one or more JS resources, and one or more JS settings.
Drupal uses a high-level principle: resources (CSS or JS) are only loaded if you tell Drupal to load them. Drupal does not load all resources (CSS/JS) on all pages, as this would negatively impact frontend performance.
Differences from Drupal 7
There are two important differences for developers from Drupal 7:
1. Only the JavaScript needed for a specific page is added to that page. Specifically, by default, Drupal doesn’t require JavaScript on most pages seen by anonymous users. This means that jQuery is no longer loaded automatically on all pages. So, if your theme needs jQuery or any other JavaScript (also defined in an asset library), you must tell Drupal by declaring a dependency on the required asset library.
2. The Drupal.settings JavaScript object is replaced by drupalSettings.
Process
Main steps to load CSS/JS resources:
1. Save your CSS or JS to a file.
2. Define a “library” which can include CSS and JS files.
3. “Attach” the library to a render array in a hook.
However, for themes, there’s an alternative to step 3: themes can load any number of asset libraries on all pages.
Defining a Library
To define one or more libraries (assets), add a *.libraries.yml file in the root of your module folder (alongside the .info.yml file). (If your module is named fluffiness, the file should be named fluffiness.libraries.yml). Each “library” in the file is an entry detailing CSS and JS files (assets), for example:
cuddly-slider: version: 1.x css: layout: css/cuddly-slider-layout.css: {} theme: css/cuddly-slider-theme.css: {} js: js/cuddly-slider.js: {}
You may notice the 'layout' and 'theme' keys for CSS that are not used for JS. These indicate the style type the CSS file belongs to.
You can assign CSS weights using 5 style levels:
- base: CSS reset/normalization plus styling HTML elements. Key assigns CSS_BASE = -200
- layout: macro page layout including grid systems. Key assigns CSS_LAYOUT = -100
- component: reusable UI components. Key assigns CSS_COMPONENT = 0
- state: styles for client-side changes in components. Key assigns CSS_STATE = 100
- theme: purely visual styles (“look and feel”). Key assigns CSS_THEME = 200
These are defined by the SMACSS standard. So, specifying 'theme' indicates that the CSS file includes theme-related styles that are purely visual. More info here. You must not use other keys as they’ll generate strict warnings.
This example assumes the actual JS file cuddly-slider.js is located in a js subfolder of your module. You can also reference JS from an external URL or include CSS files, among other options. See CDN / external libraries for details.
Note that Drupal 8 no longer loads jQuery by default on every page; only required assets are loaded. Therefore, we must declare that the cuddly-slider library depends on the library that includes jQuery. This isn’t provided by a module or theme, but by core: core/jquery is the dependency to declare. (This is formatted as extension/library name, so another library depending on cuddly-slider must declare fluffiness/cuddly-slider, with fluffiness being the module name.)
To make jQuery available to js/cuddly-slider.js, update the example as follows:
cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {} dependencies: - core/jquery
As expected, the order of listed CSS and JS files is the order in which they are loaded.
By default, Drupal attaches JS resources at the bottom of the page to avoid common issues like DOM load blocking or accessing unready DOM elements in jQuery code. If you need to include JS assets in the <head>
section, use the header option like this:
cuddly-slider: version: 1.x header: true js: js/cuddly-slider.js: {}
This ensures js/cuddly-slider.js is attached at the top of the page.
Attaching Libraries to Pages
Depending on the assets you need to load, you can attach the appropriate asset library in different ways. Some asset libraries are needed on all pages, others rarely, and some on most but not all pages.
But what matters most is that we do not decide whether to load a library based on the page (i.e., URL or route), but based on what’s rendered on the page: if the page contains '#type' => 'table', '#type' => 'dropbutton', and '#type' => 'foobar', then we load only the libraries tied to each of those #types.
But we’re not limited to just “#type”: if we want to load a specific asset library only for a specific instance of a #type, we simply attach it to the render array for that instance.
Very rarely is there a good reason to load a specific asset on all pages (e.g., analytics JS that tracks page views), regardless of what's rendered.
Attaching to a specific "#type" (for all instances)
To attach a library to a specific existing "#type", for all of its instances, use hook_element_info_alter():
function yourmodule_element_info_alter(array &$types) { if (isset($types['table'])) { $types['table']['#attached']['library'][] = 'your_module/library_name'; } }
Then clear the cache so Drupal knows about your new hook implementation.
Attaching to a render array
To attach a library to a render array (and possibly a specific instance of a "#type"), you must have access to that render array. Maybe you’re defining it, or modifying it in a hook. Either way, it might look like:
$build['the_element_that_needs_the_asset_library']['#attached']['library'][] = 'your_module/library_name';
Always use numeric keys!
You might be tempted to help Drupal avoid duplicates by using non-numeric keys:
$build['the_element_that_needs_the_asset_library']['#attached']['library']['your_module/library_name'] = 'your_module/library_name';
Don’t do this. The array merge method in Drupal will create an invalid nested array and throw warnings like:
Warning: explode() expects parameter 2 to be string, array given in Drupal\Core\Asset\LibraryDependencyResolver->doGetDependencies() Notice: Array to string conversion in system_js_settings_alter() Notice: Array to string conversion in Drupal\Core\Asset\AttachedAssets->setLibraries()
Attaching to a render array in a block plugin
Another example of library attachment: If you’re building a block plugin in your module, you can attach libraries to the render array in the build() function of your class extending BlockBase (starting with Drupal 8 beta 6):
return [ '#theme' => 'your_module_theme_id', '#someVariable' => $some_variable, '#attached' => [ 'library' => [ 'your_module/library_name', ], ], ];
Attaching to a form
Since forms are just render arrays, attaching a library works the same way:
/** * Implements hook_form_alter(). */ function yourmodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { $formObject = $form_state->getFormObject(); if ($formObject instanceof \Drupal\Core\Entity\EntityFormInterface) { $entity = $formObject->getEntity(); if ( $entity->getEntityTypeId() === 'node' && in_array($entity->bundle(), ['organisation', 'location', 'event', 'article']) ) { $form['#attached']['library'][] = 'yourmodule/yourlibrary'; } } }
Attaching a library to all (or a subset of) pages
Sometimes an asset library isn’t tied to a specific page element, but applies to the whole page. For this case, use hook_page_attachments(). A good example is from the “Contextual Links” module:
// From core/modules/contextual/contextual.module. function contextual_page_attachments(array &$page) { if (!\Drupal::currentUser()->hasPermission('access contextual links')) { return; } $page['#attached']['library'][] = 'contextual/drupal.contextual-links'; }
Attaching a library in a preprocess function
You can attach a library in a preprocess function using the '#attached' key:
function yourmodule_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'your_module/library_name'; }
Attaching a library in a Twig template
You can attach a library directly in a Twig template using the attach_library()
function. In any *.html.twig:
{{ attach_library('your_module/library_name') }}Some markup {{ message }}
Attaching a Library to Pages
Depending on the assets you need to load, you can attach the appropriate asset library in different ways. Some libraries are needed on all pages, others rarely, and others on most but not all pages.
What’s most important is that you don’t decide whether to attach a library based on which page you’re on (i.e. which URL or route), but based on what is shown on the page: if the page contains '#type' => 'table', '#type' => 'dropbutton', and '#type' => 'foobar', then only the libraries for each of those types will be loaded.
But you're not limited to just "#type": if you want to load a resource library for a specific instance of a "#type", you just attach it to that render array instance.
Rarely is there a good reason to load a resource on all pages (e.g. analytics JS that tracks page views), regardless of the “things” on the page.
Attaching to a Specific "#type" (All Instances)
Use hook_element_info_alter()
to attach a library to all instances of a specific "#type":
function yourmodule_element_info_alter(array &$types) { if (isset($types['table'])) { $types['table']['#attached']['library'][] = 'your_module/library_name'; } }
Then clear cache so Drupal recognizes your new hook implementation.
Attaching to a Render Array
To attach a library to a render array (or a specific "#type" instance), modify the array like this:
$build['my_element']['#attached']['library'][] = 'your_module/library_name';
Always use numeric keys!
You may be tempted to help Drupal by using non-numeric keys to prevent duplication:
$build['my_element']['#attached']['library']['your_module/library_name'] = 'your_module/library_name';
Don’t do that — it will result in an invalid nested array. You'll get warnings like:
Warning: explode() expects parameter 2 to be string, array given Notice: Array to string conversion
Attaching in a Block Plugin
If you're building a custom block plugin, attach libraries in the build()
method:
return [ '#theme' => 'your_module_theme_id', '#someVariable' => $some_variable, '#attached' => [ 'library' => [ 'your_module/library_name', ], ], ];
Attaching to a Form
function yourmodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { $formObject = $form_state->getFormObject(); if ($formObject instanceof \Drupal\Core\Entity\EntityFormInterface) { $entity = $formObject->getEntity(); if ($entity->getEntityTypeId() === 'node' && in_array($entity->bundle(), ['organisation', 'location', 'event', 'article'])) { $form['#attached']['library'][] = 'yourmodule/yourlibrary'; } } }
Attaching to All (or Some) Pages
Use hook_page_attachments()
for sitewide libraries. Example from the contextual module:
function contextual_page_attachments(array &$page) { if (!\Drupal::currentUser()->hasPermission('access contextual links')) { return; } $page['#attached']['library'][] = 'contextual/drupal.contextual-links'; }
Attaching in Preprocess Function
function yourmodule_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'your_module/library_name'; }
Attaching in Twig Template
Use attach_library()
in a Twig template:
{{ attach_library('your_module/library_name') }} <div>Some markup {{ message }}</div>
Attaching During Token Replacement
function your_module_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) { $replacements = []; if ($type == 'your_module') { foreach ($tokens as $name => $original) { if ($name === 'your-token') { $render_array = your_module_build_render(); $replacements[$original] = \Drupal::service('renderer')->render($render_array); $bubbleable_metadata->addAttachments(['library' => ['your_module/library_name']]); } } } return $replacements; }
Attaching in a Filter Plugin
if (...) { $result->setProcessedText(Html::serialize($dom)) ->addAttachments([ 'library' => ['filter/caption'], ]); } return $result;
Attaching Custom JS with drupalSettings
$settings = ['foo' => 'bar', 'baz' => 'qux']; $build['#attached']['library'][] = 'your_module/library_name'; $build['#attached']['drupalSettings']['yourModule']['libraryName'] = $settings;
Access in JS via drupalSettings.yourModule.libraryName.foo
.
Render arrays are cached. You may need to adjust cacheability metadata based on the data.
Adding Attributes to Script Tags
If you want to add attributes to a <script> tag, use the attributes
key in the library definition. Inside the attributes object, add key-value pairs where keys are the attribute names and values are the attribute values. If a value is true
, the attribute will be rendered without a value.
https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap: type: external attributes: defer: true async: true data-test: map-link
This results in the following markup:
<script src="https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap" async defer data-test="map-link"></script>
Disabling Aggregation
By default, local JS files are aggregated for performance. To disable this for a specific file, set its preprocess
flag to false
in the library definition:
cuddly-slider: version: 1.x js: js/cuddly-slider.js: { preprocess: false } dependencies: - core/jquery - core/drupalSettings
CDN / External Libraries
You may want to load JS from a CDN for faster delivery. You can define such external libraries like this:
angular.angularjs: remote: https://github.com/angular/angular.js version: 1.4.4 license: name: MIT url: https://github.com/angular/angular.js/blob/master/LICENSE gpl-compatible: true js: https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: type: external minified: true
Inline JavaScript
Inline JavaScript is strongly discouraged. It's better to place it in external JS files, allowing it to be cached, debugged, and compliant with Content Security Policy (CSP) headers.
Inline JS that generates markup
Instead of inline JS for ads, social buttons, or widgets, place them in a block or directly in a Twig template:
<script type="text/javascript"> ad_client_id = "some identifier"; ad_width = 160; ad_height = 90; </script> <script src="http://adserver.com/ad.js"></script>
<a class="twitter-timeline" href="https://twitter.com/username" data-widget-id="123456">Tweets by @username</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https'; if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js"; fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
Inline JS that affects the entire page
Still discouraged. Use it only for analytics or custom fonts if absolutely necessary. Use hook_page_attachments()
to insert such tags:
function fluffiness_page_attachments(array &$attachments) { $attachments['#attached']['html_head'][] = [ [ '#type' => 'html_tag', '#tag' => 'script', '#value' => 'alert("Hello world!");', '#attributes' => ['src' => ''], ], 'hello-world', ]; }
Dynamically Generated CSS and JS
Use only when absolutely necessary.
There are two scenarios:
- Generated once, reused multiple times: Use
hook_library_info_alter()
orhook_library_info_build()
to insert generated assets. - Generated on every request: Very rare and performance-heavy. Use
hook_page_attachments()
to inject <style> or <script> tags dynamically.
hook_library_info_build()
This hook allows registering dynamic libraries with PHP logic. Libraries are still cached like static ones and must still be attached to render arrays or pages.
Differences from Drupal 7
- Libraries are now defined in
*.libraries.yml
, not viahook_library_info()
. drupal_add_css()
,drupal_add_js()
, anddrupal_add_library()
are replaced by the#attached
system.Drupal.settings
is replaced bydrupalSettings
.
More Information
Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.