This article explains how Drupal turns an incoming HTTP request into the HTML (or JSON) response you see in the browser. It covers:
- A high-level beginner-friendly overview
- A deeper walkthrough of the Symfony and Drupal core code involved
- References to actual core files and methods
- A diagram-friendly flow representation
- Real-world developer use cases
This knowledge is essential for debugging, performance, and understanding how modules, controllers, themes, and APIs interact inside Drupal.
1. High-Level Flow (Beginner Friendly)
When a user visits /node/5, Drupal follows a predictable pipeline:
Browser → index.php → Kernel Boot → Routing → Controller → Render Array → Twig → HTML → BrowserSummary of steps
- Browser sends an HTTP request.
index.phpreceives it.- DrupalKernel boots the system.
- Routing determines which controller should handle the request.
- Controller returns a render array or response object.
- The render pipeline and Twig generate HTML.
- A Response object is sent back to the browser.
2. index.php - Drupal’s Front Controller
File: web/index.php
This is the single entry point for all Drupal requests.
Key code
$autoloader = require_once __DIR__ . '/../vendor/autoload.php';
$request = Request::createFromGlobals();
$kernel = new DrupalKernel('prod', $autoloader);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);What happens here
- Composer autoloader loads classes.
- Symfony
Requestobject is created. DrupalKernelinitializes the Drupal environment.- The request moves into the Symfony HttpKernel pipeline.
- The final
Responseis sent to the browser.
3. DrupalKernel - Booting Drupal
File: core/lib/Drupal/Core/DrupalKernel.php
The core entry method:
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) {
$this->boot();
return $this->container->get('http_kernel')->handle($request, $type, $catch);
}What boot() does
protected function boot() {
$this->initializeSettings();
$this->initializeContainer();
}During boot, Drupal loads:
- Module service providers
- Event subscribers
- Routing providers
- CMI configuration
- Cache backend
- Active theme information
- Dependency injection container
This is where Drupal becomes fully operational.
4. Symfony HttpKernel - Routing and Pipeline
After booting, processing is handed to Symfony’s HttpKernel.
File: vendor/symfony/http-kernel/HttpKernel.php
Routing occurs through Drupal’s router service:
$route_info = $this->container->get('router')->matchRequest($request);Route matching logic is implemented in:
core/lib/Drupal/Core/Routing/Router.phpcore/lib/Drupal/Core/Routing/Matcher/DumperMatcher.php
Routing determines:
- Which controller to call
- Parameters extracted from the path
- Required permissions and access checks
Failure results in:
- 404 Not Found
- 403 Access Denied
5. Controller Resolver - Executing Your Controller
File: vendor/symfony/http-kernel/Controller/ControllerResolver.php
Execution flow:
$controller = $this->resolver->resolve($request);
$arguments = $this->argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);Example controller:
public function content(NodeInterface $node) {
return [
'#theme' => 'node_display',
'#node' => $node,
];
}Controllers return structured data:
- Render arrays
- JSON responses
- Redirect responses
- Binary/file responses
They do not output HTML directly.
6. Render Arrays → Render Pipeline → Twig
If a controller returns a render array, Drupal turns it into HTML using the render system.
Renderer: core/lib/Drupal/Core/Render/Renderer.php
public function render($elements) {
return $this->doRender($elements);
}Theme Manager: core/lib/Drupal/Core/Theme/ThemeManager.php
$theme_hook = $this->themeRegistry->get($hook_name);Twig template execution occurs within:vendor/twig/twig/src/Template.php
Render processing includes:
- Preprocess functions
- Theme hook resolution
- Library attachments
- Lazy builders
- Cache metadata (tags, contexts, max-age)
Render caching explains why UI changes sometimes appear delayed.
7. The Response Object
After rendering, Drupal wraps the output in a Symfony Response.
return $response;Sending the response:
$response->send();Browser receives:
- HTML or JSON body
- Status code
- Headers
- Cookies
8. Full Execution Flow
web/index.php
→ DrupalKernel::handle()
→ boot()
→ load settings
→ load modules
→ build container
→ build router
→ HttpKernel::handle()
→ matchRequest()
→ resolve controller
→ execute controller
→ render array → Renderer → ThemeManager → Twig
→ Response::send()
This represents the full pipeline every request follows.
Why This Architecture Matters
A clear understanding of the request lifecycle helps developers:
- Debug routing and access issues
- Fix caching or stale render data problems
- Write efficient custom controllers
- Build middleware and event subscribers
- Diagnose multisite or config split issues
- Implement reliable API integrations such as Salesforce or Elasticsearch
Quick Summary
- Requests enter through
index.php. DrupalKernelboots the system.- Symfony handles routing and controller execution.
- Controllers return render arrays or response objects.
- Render arrays pass through Twig and theming.
- A final Symfony
Responseis returned to the browser.
Real-World Use Cases
- Creating custom route/controller pages
- Returning JSON for React or Vue frontends
- Adding middleware for security or logging
- Debugging missing or stale blocks
- Tracing Webform to API submission flows
- Working with cache invalidation, Redis, and dynamic page cache
- Troubleshooting performance bottlenecks in kernel or routing