A form array is one of the most nested, sprawling data structures you'll ever work with in Drupal — a form with ten fields can easily produce a `$form` array with hundreds of keys once you count `#type`, `#title`, `#attributes`, `#states`, and every other property nested under every element. Trying to debug that by eye, scrolling through a `var_dump()`, is like trying to find one mislabeled folder in a filing cabinet by reading every page inside every folder. `kint()` and Symfony's `dump()` are the tools that turn that filing cabinet into a searchable, collapsible tree you can actually navigate.
What you'll learn in this lesson
- Why raw `print_r()`/`var_dump()` output becomes unreadable for anything as deeply nested as a form array
- How to install and enable the Devel module to get `kint()`
- How to drop a debug call into `buildForm()` without breaking the form
- The difference between `kint()` (Devel's formatter) and `dump()` (Symfony's built-in VarDumper)
Why form arrays specifically need a real inspector
Every render array property starts with `#`, and Form API elements nest deeply — a `#type => 'fieldset'` element contains child elements, each of which can carry its own `#states`, `#attributes`, `#ajax`, and so on. `print_r($form)` dumps all of that as flat, unindented text with no way to collapse anything you're not interested in. On a real form, that output can run to thousands of lines. `kint()` renders the same data as an interactive, collapsible tree in the page itself — you expand only the branches you care about.
Installing the Devel module
composer require drupal/devel
drush en devel -y
Devel is a contributed module, not part of core, so it needs to be pulled in via Composer like any other dependency. Once enabled, `kint()` becomes available as a global function anywhere in your module's PHP — no `use` statement required, since Devel registers it as a procedural helper.
Dropping kint() into a form's build method
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class InspectForm extends FormBase {
public function getFormId() {
return 'inspect_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form['example'] = [
'#type' => 'textfield',
'#title' => $this->t('Example field'),
];
kint($form);
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state) {}
}
Placing `kint($form)` right before `return $form;` lets you inspect the array exactly as it will be rendered — every element you've added, plus whatever the parent class or any `hook_form_alter()` implementation elsewhere on the site has already contributed. The `kint()` output appears inline on the page itself, above the form, as a nested tree you can click through.
kint() vs. dump() — which one to reach for
`kint()` comes from the Devel module specifically and produces Drupal-aware, richly formatted output — object methods, inherited properties, and Drupal entity internals are all rendered legibly. `dump()` comes from Symfony's VarDumper component, which Drupal core already depends on, so it's available even without Devel installed. It's a lighter-weight, slightly less Drupal-specific formatter — genuinely useful when you can't install a new module (a locked-down production-adjacent environment, say) but still need better-than-`print_r()` output.
Quick check: you need to inspect a form array on a staging site where you can't install new contributed modules. Which function do you reach for — `kint()` or `dump()`? (Answer: `dump()` — it ships with Symfony's VarDumper, a core dependency, so it's already available with zero new modules installed. `kint()` requires the Devel module specifically.)
Key takeaways
- `print_r()`/`var_dump()` become unreadable on deeply nested form arrays — reach for a real inspector instead.
- `kint()` requires the Devel module (`composer require drupal/devel` + enable) but gives the richest, most Drupal-aware formatting.
- `dump()` ships with Symfony's VarDumper, a core dependency, so it works even without installing anything extra.
- Place your debug call right before `return $form;` in `buildForm()` to see the array exactly as it will render, including anything added by `hook_form_alter()`.
- Never leave a `kint()`/`dump()` call in code you ship — it exposes internal data on the live page.
Coming up next
Inspecting the form's structure is only half the debugging story — the other half is figuring out why a validation rule isn't firing the way you expect. In the next lesson, we'll look at exactly that: tracing why `setErrorByName()` calls do or don't appear, and how `$form_state` carries validation state between requests.