The Renderer Service
When to Use
When you need to programmatically convert render arrays to HTML strings -- tokens, emails, feeds, AJAX responses, testing, or anywhere outside the normal page rendering flow.
Steps: Using the Renderer Service
1. Get the renderer service
$renderer = \Drupal::service('renderer');
2. Choose the right rendering method
| Method | Use When | Context |
|---|---|---|
renderRoot(&$elements) |
Final rendering for HTTP response | Cannot be nested; throws exception if called within another renderRoot() |
renderInIsolation(&$elements) |
Need HTML string inside another rendering process | Creates isolated render context; can be called within renderRoot() |
render(&$elements, $is_root_call) |
Internal/recursive rendering | Usually called by Drupal core, not directly by modules |
3. Render the array
// Example: Render for email
$build = [
'#theme' => 'mymodule_email_template',
'#user' => $user,
'#content' => $message,
];
$html = $renderer->renderInIsolation($build);
// Returns MarkupInterface object; cast to string or use directly
$email_body = (string) $html;
Reference: core/lib/Drupal/Core/Render/RendererInterface.php (lines 10-62) -- method documentation
Pattern: Rendering in a Service
namespace Drupal\mymodule\Service;
use Drupal\Core\Render\RendererInterface;
class EmailBuilder {
protected $renderer;
public function __construct(RendererInterface $renderer) {
$this->renderer = $renderer;
}
public function buildWelcomeEmail($user) {
$build = [
'#theme' => 'mymodule_welcome_email',
'#user' => $user,
];
return $this->renderer->renderInIsolation($build);
}
}
Services YAML:
services:
mymodule.email_builder:
class: Drupal\mymodule\Service\EmailBuilder
arguments: ['@renderer']
Pattern: Capturing Bubbleable Metadata
use Drupal\Core\Render\RenderContext;
$context = new RenderContext();
$output = $renderer->executeInRenderContext($context, function() use ($build, $renderer) {
return $renderer->render($build);
});
// Check if metadata was collected
if (!$context->isEmpty()) {
$metadata = $context->pop(); // BubbleableMetadata object
$cache_tags = $metadata->getCacheTags();
$attachments = $metadata->getAttachments();
}
Reference: core/lib/Drupal/Core/Render/RendererInterface.php (lines 358-386) -- executeInRenderContext() documentation
Decision: When to Render Programmatically
| Scenario | Approach | Rationale |
|---|---|---|
| Controller return value | Return render array directly | Drupal's main content renderer handles it automatically |
| Block build method | Return render array | Block system handles rendering |
| AJAX callback | Return AjaxResponse with render arrays |
AJAX system renders them client-side |
| Email body | Use renderInIsolation() |
Need HTML string, no assets/attachments |
| Token replacement | Use renderInIsolation() |
Tokens must resolve to strings |
| RSS/Atom feed | Use renderInIsolation() |
Feed XML doesn't support CSS/JS attachments |
| Testing | Use renderRoot() or renderInIsolation() |
Need actual HTML to assert against |
Common Mistakes
- Calling
renderRoot()inside anotherrenderRoot()-- Fatal exception; userenderInIsolation()instead - Using
render()directly instead ofrenderRoot()-- Metadata doesn't bubble to response; userenderRoot()for final rendering - Rendering too early in preprocess functions -- Pass render arrays to Twig instead; let Twig render them
- Not capturing bubbleable metadata when needed -- Assets/cache tags lost; use
executeInRenderContext()if you need them - Rendering user-submitted content without proper context -- XSS risk; ensure render arrays have proper
#markupfiltering
See Also
- Render API Overview for when NOT to render programmatically
- Twig Integration for automatic rendering in templates
- Reference:
core/lib/Drupal/Core/Render/Renderer.php-- full implementation