Developer Journal

Advanced 9 min read

Debugging Drupal Assets From Configuration to the Browser

A layer-by-layer workflow for tracing Drupal CSS and JavaScript from library definitions and render attachments to network requests and the final DOM.

Last updated August 8, 2026

When a Drupal CSS or JavaScript feature fails, "the asset did not load" is only one possible explanation. Drupal assets move through package installation, library discovery, render attachments, cache metadata, HTML generation, HTTP delivery, JavaScript execution, and finally the DOM. Debugging becomes much faster when each boundary is tested separately.

Asset bugs are pipeline bugs

A browser can show a missing stylesheet or an uninitialized JavaScript feature, but that does not identify the layer responsible. A bad URL might come from an old library definition, stale generated HTML, a CDN, browser state, or custom code. A correctly downloaded JavaScript file can still fail because its configuration is missing or because it intentionally skips the target element.

I use the following model when tracing a Drupal asset:

Composer / deployed files
  -> Drupal library discovery
  -> render or filter attachment
  -> bubbleable metadata
  -> final HTML
  -> HTTP request
  -> JavaScript execution
  -> DOM and computed CSS

The goal is to find the first boundary where expected state becomes incorrect.

Layer 1: prove the code that is deployed

Start with the environment itself. Before inspecting cache behavior, confirm the commit and package version that are actually running:

git log -1 --oneline

composer show drupal/highlightjs_input_filter

drush status --fields=drupal-version,bootstrap,db-status

This sounds basic, but it prevents a common failure mode in debugging: reading one version of a file locally while the server is executing another version.

If Composer metadata reports the expected package version, inspect the actual installed file when the behavior still looks inconsistent. Package metadata and filesystem contents should tell the same story.

Layer 2: ask Drupal what the library means

Drupal parses *.libraries.yml definitions into its library-discovery system. When an asset URL looks suspicious, inspect that parsed definition instead of stopping at the YAML file:

$library = \Drupal::service('library.discovery')
  ->getLibraryByName(
    'highlightjs_input_filter',
    'highlightjs-copy'
  );

print_r($library);

This shows what Drupal currently believes about JavaScript files, CSS files, versions, dependencies, remote assets, and attributes.

If the parsed definition differs from the source file, cache or extension discovery becomes a strong lead. If the parsed definition is already correct, changing the library YAML is unlikely to solve the current problem.

Layer 3: find who attaches the library

A valid library definition does nothing until something attaches it. The attachment might come from a render array, a theme, a Twig template, a preprocess function, a controller, a block, or a text filter.

Drupal text filters can attach libraries and runtime JavaScript configuration through their FilterProcessResult. A filter can return unchanged text while still adding assets:

$result->addAttachments([
  'library' => [
    'example/highlighter',
  ],
  'drupalSettings' => [
    'exampleLanguages' => $languages,
  ],
]);

This is important because the HTML string produced by the filter is only part of its output. The associated attachments are bubbleable rendering metadata and must survive the rest of the render pipeline.

If a standalone render test works but a complete page does not, inspect whether the child render element is still being rendered normally or whether custom code flattened it into a string too early.

Layer 4: inspect the final response

Once Drupal configuration and attachment logic look correct, inspect the actual public HTML. This is the boundary between Drupal and the browser:

curl -sS https://www.example.com/example-page \
  | grep -iE \
  'example-library|drupal-settings|highlight|script|stylesheet'

For configurable JavaScript, confirm both the script tag and its drupalSettings values. A JavaScript file can load perfectly and still be unable to initialize because PHP never supplied the runtime configuration it expects.

If the final HTML contains the wrong URL, the problem still exists on the server side. If the final HTML contains the correct URL while DevTools reports a different request, the investigation can move toward browser state, another script, or an intermediate cache.

Layer 5: use the Network panel as transport evidence

The browser Network panel answers transport questions:

  • Which exact URL was requested?
  • What initiated the request?
  • Was the response 200, 304, 404, blocked, or redirected?
  • Was the resource served from memory cache, disk cache, a service worker, or the network?
  • Did a dynamically imported ES module fail independently from the main script?

A 200 response proves delivery, not correct execution. Once all required assets arrive, stop treating the problem as a missing-file problem unless JavaScript reports a parsing or module-loading error.

Layer 6: inspect execution and the resulting DOM

JavaScript debugging becomes much more useful when the expected state is converted into explicit checks.

For a Drupal behavior, I might inspect whether the behavior exists, whether its settings exist, and whether the target elements received the classes or markup initialization should create:

({
  behavior: typeof Drupal?.behaviors?.highlightInit?.attach,

  settings: {
    baseUrl: drupalSettings.highlightJsBaseUrl,
    languages: drupalSettings.highlightJsLanguages
  },

  blocks: [...document.querySelectorAll('pre > code')].map((el) => ({
    classes: el.className,
    highlighted: el.classList.contains('hljs'),
    childSpans: el.querySelectorAll('span[class^="hljs-"]').length
  }))
})

This separates "the library loaded" from "the library did its job."

If the JavaScript object exists but the DOM is unchanged, inspect initialization conditions and target markup. If the DOM is correct but the page still looks wrong, move to computed styles and CSS specificity.

Cache is not one thing

"Clear the cache" is useful only when the relevant cache is understood. A Drupal asset problem may involve service and extension discovery, render cache, Dynamic Page Cache, generated asset aggregates, a reverse proxy, a CDN, or the browser.

A Drupal cache rebuild is appropriate after changes to services, plugins, theme discovery, and many render-related definitions. It cannot force a browser to forget every cached response, and it should not be used as evidence that a library definition was wrong.

I prefer this sequence:

  1. Inspect the current state.
  2. Form a cache-specific hypothesis.
  3. Rebuild or bypass that cache.
  4. Inspect the same state again.

If the before and after results are identical, cache was probably not the explanation.

CSS needs the same evidence-driven treatment

Asset debugging does not end when JavaScript initializes. A plugin can create correct markup and still conflict with a theme rule.

In one case, a copy-button plugin translated its hidden control about 63 pixels beyond the right edge of a code block. The plugin expected the wrapper to clip that overflow, but the theme had a more specific overflow: auto rule. The element's scrollWidth exceeded its clientWidth by almost exactly the same amount as the button's transform.

That numerical match was much stronger evidence than visually experimenting with random overflow values.

Turn the debugging steps into verification

Once an asset problem is solved, some of the diagnostic commands are worth keeping as deployment checks. A production verification script can report the deployed commit, module version, relevant configuration, library discovery, maintenance state, and public HTTP responses.

The value is not that every deployment needs an enormous diagnostic dump. The value is that the important assumptions become inexpensive to prove when a regression appears.

A practical debugging order

  1. Confirm the deployed commit and package version.
  2. Inspect the installed asset definition.
  3. Inspect Drupal library discovery.
  4. Identify the code that attaches the library and settings.
  5. Inspect the final public HTML with curl.
  6. Confirm the exact requests in the browser Network panel.
  7. Inspect JavaScript state and the resulting DOM.
  8. Inspect computed CSS and geometry if the DOM is correct but presentation is not.
  9. Use cache invalidation only against a specific hypothesis.

Following the pipeline makes debugging more mechanical. Each successful check eliminates an entire category of explanations.

Key Takeaways

  • A Drupal asset library has to be defined, attached, rendered, delivered, executed, and applied successfully.
  • Drupal library discovery is more authoritative than assuming the YAML file has been parsed the way you expect.
  • Inspect the final HTML before blaming the browser.
  • Use the Network panel to prove delivery and the DOM to prove execution.
  • Treat Drupal caches, proxy caches, CDNs, and browser caches as different systems.
  • Preserve useful diagnostic checks as lightweight deployment verification.

Further Reading