Twig for WordPress Developers
Twig is a templating language for PHP. In Lumbermill, all HTML lives in .twig files instead of PHP files. PHP handles data — Twig handles output.
If you have written WordPress themes with the_title(), the_content(), and get_field() directly in PHP template files, this guide will map what you already know onto how Twig works.
Why Twig?
Section titled “Why Twig?”Standard WordPress mixes PHP logic and HTML in the same file. This works but gets messy fast. Twig enforces a clean split:
- PHP file — fetches data, builds a
$contextarray, callsTimber::render() - Twig file — receives the context and renders HTML, no PHP logic
This means templates are easier to read, and data flow is explicit — if a variable isn’t in $context, it won’t appear in the template.
Syntax Basics
Section titled “Syntax Basics”Twig uses three types of tags:
| Tag | Purpose | Example |
|---|---|---|
{{ }} | Output a value | {{ post.title }} |
{% %} | Logic (if, for, block, etc.) | {% if post.title %} |
{# #} | Comment (not rendered) | {# TODO: fix this #} |
WordPress → Twig Translation
Section titled “WordPress → Twig Translation”| WordPress (PHP) | Twig |
|---|---|
the_title() | {{ post.title }} |
the_content() | {{ post.content }} |
the_permalink() | {{ post.link }} |
the_excerpt() | {{ post.excerpt }} |
get_the_ID() | {{ post.id }} |
the_post_thumbnail_url() | {{ post.thumbnail.src }} |
bloginfo('name') | {{ site.name }} |
bloginfo('url') | {{ site.url }} |
get_template_directory_uri() | {{ theme.link }} |
get_field('my_field') | passed via $context in PHP, then {{ my_field }} |
Conditionals
Section titled “Conditionals”{% if post.title %} <h1>{{ post.title }}</h1>{% endif %}{% if user %} <p>Welcome, {{ user.name }}</p>{% else %} <p>You are not logged in.</p>{% endif %}{% for post in posts %} <article> <h2>{{ post.title }}</h2> <p>{{ post.excerpt }}</p> </article>{% endfor %}Use loop.index for the current iteration number (1-based):
{% for item in items %} <p>{{ loop.index }}. {{ item.title }}</p>{% endfor %}Template Inheritance
Section titled “Template Inheritance”This is the biggest difference from standard WordPress themes. Instead of including a header and footer at the top and bottom of every file, Twig uses inheritance.
views/layouts/base.twig defines the full HTML document with named blocks:
{# views/layouts/base.twig #}<!doctype html><html><head> <title>{{ site.name }}</title></head><body> {% block content %}{% endblock %}</body></html>Every page template extends this layout and fills in the blocks:
{# views/templates/services.twig #}{% extends 'layouts/base.twig' %}
{% block content %} <main> <h1>{{ post.title }}</h1> {{ post.content }} </main>{% endblock %}You never call get_header() or get_footer() — the base layout handles that.
Includes
Section titled “Includes”To include a reusable partial:
{% include 'partials/card.twig' %}Pass variables to the partial:
{% include 'partials/card.twig' with { title: post.title, image: post.thumbnail } %}Common Mistakes
Section titled “Common Mistakes”Variable not showing up in Twig
The variable was not added to $context in the PHP file.
// ❌ this won't be available in Twig$my_posts = Timber::get_posts(['post_type' => 'news']);
// ✅ add it to context$context['my_posts'] = Timber::get_posts(['post_type' => 'news']);Trying to call PHP functions directly
Twig is not PHP. You cannot call arbitrary PHP functions inside {{ }}.
{# ❌ won't work #}{{ get_field('my_field') }}
{# ✅ fetch in PHP, pass through context #}{{ my_field }}Printing HTML safely
By default, Twig escapes output. To render HTML stored in a variable, use the raw filter:
{{ post.content | raw }}Only use raw on content you trust (e.g. WordPress post content, not user input).
Next Steps
Section titled “Next Steps”- Context guide — local vs global context, adding your own data
- Class Maps — extending Timber’s Post/Term objects with custom methods
- Custom Twig functions and filters — adding your own helpers
- Twig documentation — full language reference

