Twig: Template Inheritance, Macros & Best Practices
Template Inheritance
{# base.html.twig #}
<html>
<body>
<header>{% include 'partials/nav.html.twig' %}</header>
{% block content %}{% endblock %}
<footer>...</footer>
</body>
</html>
{# page.html.twig -- fills in just the content block, shared
header/footer never need to be duplicated #}
{% extends 'base.html.twig' %}
{% block content %}
<p>Page-specific content here.</p>
{% endblock %}include vs. extends
{# include: reusable fragment embedded at a specific point #}
{% include 'partials/product-card.html.twig' with {product: item} %}
{# extends: overall page LAYOUT relationship -- one parent, many
child pages customizing specific named blocks #}Macros: Reusable Parameterized Markup
{% macro input(name, value, type = 'text') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{# usage -- avoids repeated markup across many form templates #}
{{ forms.input('email', user.email) }}set, Concatenation & verbatim
{% set fullName = user.firstName ~ ' ' ~ user.lastName %}
{{ fullName }}
{# ~ concatenates -- Twig uses . for property/method access #}
{% verbatim %}
Documentation showing literal {{ variable }} syntax as example text
{% endverbatim %}Keeping Logic Out of Templates
Compute business logic in PHP controllers/services and pass the result into the template, rather than embedding it directly. Twig's deliberately restricted syntax keeps templates focused on presentation -- easier to test and safer for designers to edit without breaking application behavior. Sandbox mode further restricts available functions/filters when template content comes from a less-trusted source.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free