Integrating Devel for module debuggingfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Logging tells you what happened; sometimes you need to see what's happening right now — the exact contents of a variable mid-request, which hooks actually fired for a page, or a quick way to dump an object without writing throwaway debug code you'll delete later. The Devel module is the standard contrib tool the whole Drupal community reaches for to do exactly that.

What you'll learn in this lesson

  • What Devel actually gives you beyond core Drupal
  • The two functions you'll use constantly: dpm() and kint()
  • Devel's Kernel/Container introspection pages, for seeing what services and routes actually exist
  • Why Devel must never be enabled on a production site

Installing and the two workhorse functions

composer require drupal/devel
ddev drush en devel devel_kint_extras -y
// Dump a variable to the on-screen message area — survives a redirect,
// so it's the one to reach for inside a submitForm() or controller.
dpm($node, 'Node being processed');

// Dump a variable inline, right where the code runs, using the Kint library —
// produces a rich, collapsible, syntax-highlighted tree in the page itself.
kint($node);

// Dump straight to the PHP error log instead of the page — useful for AJAX
// callbacks or anywhere you can't inject visible markup into the response.
\Drupal::logger('my_module')->debug('
' . print_r($node->toArray(), TRUE) . '
');

dpm() ("devel print message") queues its output through Drupal's own messenger service, so it appears as a status message on the next rendered page — the only one of these that survives a form submission's redirect. kint() renders immediately, inline, with a genuinely excellent interactive tree view for exploring deeply nested objects and arrays; reach for it whenever the code path in question actually returns a normal, renderable page.

Beyond variable dumping

  • Routes list (/devel/routes) — search every registered route on the site by path or name, invaluable when you're not sure which route a given URL actually resolves to.
  • Services list (/devel/services) — browse every service in the container, confirm a service ID exists and see its class before you try to inject it.
  • Hooks list — see every hook implementation that fired for the current page, in the order they ran — genuinely useful for understanding why some other module's hook_form_alter() is stomping on your form.
  • Mail system switcher — redirect all outgoing site email to the screen instead of actually sending it, so you can test email-triggering code without spamming real inboxes.
Devel is a development dependency, full stop: require it with composer require --dev drupal/devel if your workflow distinguishes dev/prod dependencies, and never let it reach a production deploy. It intentionally exposes internal system details (service definitions, raw variable contents, potentially sensitive data mid-request) that have no business being visible to a real site visitor — this isn't a performance concern, it's a genuine information-disclosure risk.

Quick check: you're debugging an AJAX callback that returns a JSON response, and kint($data) doesn't seem to show up anywhere. Why not, and what should you use instead? (Answer: kint() injects markup into a normal HTML page render — an AJAX response is pure JSON with no page to inject into. Use \Drupal::logger()->debug() or Devel's dpm() variant that queues to watchdog instead, so you can inspect the value outside the response itself.)

Key takeaways

  • dpm() queues output through the messenger — the right choice when the current request redirects (like after a form submit).
  • kint() renders an interactive tree inline — the right choice for a normal page render you can inspect directly.
  • Devel's routes/services/hooks browsers answer "does this exist, and in what order does it run" questions faster than grepping the codebase.
  • Devel must never ship to production — require it as a dev dependency and keep it disabled outside local/staging environments.

Coming up next

Devel tells you what's happening; it doesn't tell you how fast. Next, we look at profiling — measuring where your module's execution time and memory actually go, so you can fix the part that's genuinely slow instead of guessing.