Rendering Twig templates programmatically in Drupal 8

From time to time, I have the need to take a Twig template and a set of variables, render the template, replacing all the variables within, and then get the output as a string. For example, if I want to have a really simple email template in a custom module which has a variable for first_name, so I can customize the email before sending it via Drupal or PHP, I could do the following in Drupal 7:

<?php
$body
= theme_render_template(drupal_get_path('module', 'my_module') . '/templates/email-body.tpl.php', array(
 
'first_name' => 'Jane',
));
send_email($from, $to, $subject, $body);
?>

In Drupal 8, there is no theme_render_template() function, since the template engine was switched to Twig in this issue. And until today, there was no change record indicating the fact that the handy theme_render_template() had been replaced by a new, equally-handy twig_render_template() function! Thanks to some help from Tim Plunkett, I was able to find this new function, and after he pushed me to do it, I created a new change record to help future-me next time I go looking for theme_render_template() in Drupal 8: theme_render_template changed to twig_render_template.

In Drupal 8, it's extremely similar to Drupal 7, although there are two additions I made to make it functionally equivalent:

<?php
$markup
= twig_render_template(drupal_get_path('module', 'my_module') . '/templates/email-body.html.twig', array(
 
'my-variable' => 'value',
 
// Needed to prevent notices when Twig debugging is enabled.
 
'theme_hook_original' => 'not-applicable',
));
// Cast to string since twig_render_template returns a Markup object.
$body = (string) $markup;
send_email($from, $to, $subject, $body);
?>

If you are rendering a template outside of a normal page request (e.g. in a cron job, queue worker, Drush command, etc.) the Twig theme engine might not be loaded. If that's the case, you'll need to manually load the Twig engine using:

<?php
// Load the Twig theme engine so we can use twig_render_template().
include_once \Drupal::root() . '/core/themes/engines/twig/twig.engine';
?>

I shall go forth templating ALL THE THINGS now!

Comments

Using the service requires a render array and/or a bunch of other scaffolding that I do not want to have to set up to just take a template, take some variables, and run the template through the template engine.

Note that if you're considering doing something like this for anything that would render on a Drupal page, then just don't. The technique outlined in this blog post should only be used if you're doing something like what I'm doing—I'm generating a configuration payload from some variables from Drupal nodes and then passing that along to an external service.

Hi Jeff -- thanks for all the great content (has been very helpful on different projects!)
I have a content type that has about twenty percent Fords and eighty percent Chevys. If it's a Ford, then I want the system to automatically create a twig template which will allow me to put a small blue label at the top of the page, 'FORD Content.' If it's a Chevy, Chrysler or any other content, no template is necessary. The choice of Ford, Chevy, etc. is made by the content author from a dropdown field in the /node/add/automobile page.

Can I use this mechanism to achieve that? Or should I use something else? This content will be rendered on a Drupal page, and you said in the post that in that case, we just shouldn't?

It would probably be better to use something like views or a custom block instead of this technique, because that way the rendering would not be as complicated/custom.

You could have a block that appears on the page and has certain content for certain values of a field on the node.