Debug a Drupal site
“Why is my change not showing?” is the most common first-week Drupal question, and the answer is almost always one of two things: a cache served the old output, or an error happened and production-style settings hid it. This page sets up a local site that hides nothing, then gives you the tools for everything the settings don’t catch.
A development configuration where errors display with backtraces, templates and render output are not cached, and you can read logs and attach a debugger. Apply this to local environments only; every switch on this page trades speed and safety for visibility.
Prerequisites
Section titled “Prerequisites”- A local site per Set up your environment and Install Drupal core.
- For the Xdebug step, an IDE or editor with a debug adapter (PhpStorm and VS Code both work out of the box).
Turn on settings.local.php
Section titled “Turn on settings.local.php”Core ships a ready-made local development configuration; you only have to activate it. First, copy the example file into place:
cp web/sites/example.settings.local.php web/sites/default/settings.local.phpThen open web/sites/default/settings.php, scroll to the end, and uncomment the block that loads it. It sits just above the include block that DDEV adds at the bottom of the file:
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { include $app_root . '/' . $site_path . '/settings.local.php';}Rebuild caches (ddev drush cache:rebuild) and your site now runs with the file’s defaults:
- Verbose errors:
error_levelis forced toverbose, so exceptions render with a backtrace instead of “The website encountered an unexpected error”. - No asset aggregation: CSS and JavaScript are served as individual files, so the file you edit is the file the browser loads.
- Development services: the file registers
web/sites/development.services.ymlas an extra container definition. That file enables cacheability debug headers (X-Drupal-Cache-TagsandX-Drupal-Cache-Contextson responses, see Caching), defines the null cache backend used below, and swaps in a lenient config schema checker that logs schema errors instead of crashing. - rebuild.php access: with
rebuild_accessenabled, visiting/core/rebuild.phprebuilds caches even when the site is too broken to serve pages.
The example file also contains commented-out blocks worth reading: disabling the render cache, page cache, and dynamic page cache by pointing their bins at the null backend. Prefer the form in the next step for that job; it does the same thing without editing PHP.
Use the development settings form
Section titled “Use the development settings form”Visit Administer → Configuration → Development → Development settings/admin/config/development/settings. The form (DevelopmentSettingsForm, provided by the System module) has two switches:
- Do not cache markup disables the render cache, dynamic page cache, and page cache in one stroke. Your template and render array (the structured arrays Drupal turns into HTML) changes now show on the next request.
- Twig development mode exposes two checkboxes: Twig debug mode, which enables the
dump()function, wraps every rendered template in HTML comments naming the template file and the suggestions Drupal considered, and recompiles templates when they change; and Disable Twig cache, which recompiles every template on every render.
If you do any theming, turn Twig debug mode on now; the template-suggestion comments are the single highest-value debugging feature in Drupal’s front end.
These switches live in the key-value store, not in configuration. That is deliberate: they survive cache rebuilds but never end up in a configuration export, so you cannot accidentally deploy “do not cache markup” to production. The form sets the twig.config parameters for you, so you never edit services.yml by hand for this.
Rebuild caches at the right moments
Section titled “Rebuild caches at the right moments”With markup caching off, you still need cache rebuilds, because Drupal also caches discovery: which routes, services, plugins, hooks, and templates exist.
ddev drush cache:rebuildThe rule: editing code inside a function or method needs no rebuild, and neither does editing a template once Twig development mode is on. Anything Drupal discovers needs one: any change to a YAML file (routes, services, libraries, *.info.yml), to a plugin’s attribute, or to the list of hook implementations, and any new file, including a new template. If your new code seems invisible, rebuild before you doubt the code. When the site is too broken for Drush to bootstrap, use vendor/bin/dr cache:rebuild (Drupal 11.4 and later) or /core/rebuild.php.
Read the logs
Section titled “Read the logs”Anything Drupal catches, it logs. The Standard profile installs the Database Logging module, which records log entries to the database and shows them at Administer → Reports → Recent log messages/admin/reports/dblog. Each entry links to a detail page with the full message and backtrace; filter by type php to see only PHP errors. From the terminal (watchdog is the historical name of Drupal’s database log):
ddev drush watchdog:show --count=20Two failure classes never reach the database log: fatal errors that kill the request before logging runs, and errors in the logging path itself. For those, read the server logs directly:
ddev logs -s webYour own code logs through the same pipeline. Inject the logger.factory service (or call $this->getLogger('my_module') in controllers and forms) and the messages show up in the same report; the logging how-to covers patterns and severity levels.
Control error display
Section titled “Control error display”The error display level lives at Administer → Configuration → Development → Logging and errors/admin/config/development/logging: None, Errors and warnings, All messages, or All messages, with backtrace information. It controls display only; logging happens regardless. Note that settings.local.php pins this to verbose with a $config override, which takes precedence over whatever the form saves; the override is what keeps errors visible even after you import production configuration into a local site.
Step-debug with Xdebug
Section titled “Step-debug with Xdebug”When logs and dump() stop being enough, attach a real debugger. DDEV bundles Xdebug, disabled by default because it slows every request:
ddev xdebug onSet a breakpoint in your IDE, tell it to listen for PHP debug connections (port 9003), and reload the page. Execution stops at your breakpoint with the full call stack and variables, which is the fastest way to learn how a request actually moves through Drupal: put one breakpoint in your code and walk up the stack. Turn it off when you are done:
ddev xdebug offVerify
Section titled “Verify”- Trigger an error (for example, add
{{ no_such_function() }}to a Twig template you are working on, or visit a page you know throws). You see the exception with a backtrace, not a generic error page. - With Twig debug mode on, view any page’s source. You see comments like
<!-- THEME DEBUG -->,BEGIN OUTPUT from ..., and aFILE NAME SUGGESTIONSlist per template. - Run
curl -sI https://my-site.ddev.site | grep -i x-drupal. The response includesX-Drupal-Cache-TagsandX-Drupal-Cache-Contextsheaders. /admin/reports/dblogshows recent entries, including any error you triggered above.
Variations
Section titled “Variations”- Disable a single cache bin instead of all markup caching: uncomment the relevant block in
settings.local.php, for example$settings['cache']['bins']['render'] = 'cache.backend.null';. Useful when you want page caching realistic but render caching out of the way. - Test with caches on: before calling work finished, re-enable caching and verify your output carries the right cacheability metadata; the debug headers from
development.services.ymlshow exactly which cache tags and contexts each page has. The caching concept explains how to read them.
Related
Section titled “Related”- Caching is the concept behind every “stale output” symptom on this page.
- Logging covers writing to the log from your own code.
- Essential tools introduces the Drush and core CLI commands used here.
- Twig in Drupal builds on Twig debug mode for template work.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.