Jeremiah Sturgill

code etc.

Twig template macro arguments

I’ve been working with Twig recently for a project, and wound up creating a variety of macro helpers for the project’s templates.

{% macro render(slug) %}
   {# do all the things #}
{% endmacro render %}

I regularly came across cases where I wanted the macros to do something slightly different. It didn’t make sense to create an entirely new macro when I could just pass in a flag to trigger the alternate path.

{% macro render(slug, contenttype) %}
   {# do all the things #}
{% endmacro render %}

But once you start adding logic it can be hard to stop. Before you know it you wind up with monsters!

{% macro render(slug, contenttype, wrapper, hidden, classes) %}
   {# do all the things #}
{% endmacro render %}

One common pattern for this sort of issue is to pass in a dictionary of parameters that is merged with a dictionary of defaults. It shows up in a lot of places. This pattern can be implemented in PHP by calling array_merge.

class Something {

    private foo_defaults = [
        'foo' => 'bar',
        'biff' => 'boom'

    public function render($args) {
        $settings = array_merge($this->render_defaults, $args);

jQuery provides a special method, .extend(), to facilitate the same thing:

// from
var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };
// Merge defaults and options, without modifying defaults
var settings = $.extend( {}, defaults, options );

And as a sensible templating language, Twig has this covered just as simply. Its merge filter does the trick nicely:

{% macro render(slug, args) %}
    {% set args_defaults = {
        'contenttype' : 'post',
        'wrapper' : 'div',
        'hidden' : false,
        'class' : 'post single-post unicorn rainbow'

    {# create the settings that will be used #}
    {% if args is iterable %}
        {% set settings = args_defaults|merge(args) %}
    {% else %}
        {% set settings = args_defaults %}
    {% endif %}

    {# do all the things #}
{% endmacro render %}

It keeps the common path simple and concise

{{ render('hello-world') }}

while allowing for extra flexibility without being overly verbose.

{{ render('hello-world', {'hidden':true}) }}