
TwigBits: A Quick Reference List for Craft CMS Developers
Twig is the templating language used by Craft CMS to render web pages. We use
Twig
every day to list recent blog posts, output dynamic content, render forms from popular Craft plugins, and even add helpful publishing hints inside the control panel.
While Twig is remarkably robust on its own,
Craft extends it
further with added
filters
,
functions
, and
tags
, making it even more powerful. Our engineering team has compiled this list of “TwigBits” that we reference often. If you are new to templating in Craft, we hope you find a tidbit or two that you can use as well.
Table of Contents
- Reading Craft's config settings
- Global variables and services
- Manipulating HTML
- Frequent filters
- Templating tips
- Debugging
Reading Craft's config settings
All of Craft's environment variables and
General Config Settings
can be accessed inside a Twig template. You can reference these values to check the environment your site is running in, display content conditionally based on a config setting, or leverage a custom config item in your Twig code.
To access these settings, use the
getenv() / getEnv() function and the
craft.app.config.general service. Here are a few examples we reach for regularly:
{# access a key, id, or variable you've defined in /.env #}
{{ getenv('MARKERIO_DESTINATION') }}
{# add code that runs in only a certain ENVIRONMENT #}
{% if craft.app.config.env == 'staging' %}
...staging code only...
{% endif %}
{# create a condition based on a general config value #}
{% if craft.app.config.general.disallowRobots %}
...code for no robots here...
{% endif %}
{# for Craft 4 in /config/custom.php #}
{# return [
# 'paginationLimit' => 12
# ] #}
{% set entries = craft.entries({
limit: craft.app.config.custom.paginationLimit
}).all() %}
{# for Craft 3 in /config/general.php #}
{# '*' => [
# ...
# 'custom' => [
# 'paginationLimit' => 12,
# ],
# ] #}
{% set entries = craft.entries({
limit: craft.app.config.general.custom.paginationLimit
}).all() %}In Craft 4,
custom config settings
live in a dedicated file such as /config/custom.php, while in Craft 3 they can be added
alongside your general config settings. You may also want to explore the new
fluent config syntax
available in Craft 4.2+.
Global variables and services
These are a few of the global variables and services we reference dozens of times when building a Craft site.
{{ siteUrl }}
,
siteUrl() and
{{ siteName }}As it says on the tin, the siteUrl variable outputs the URL of the current site—but note that
there's also a siteUrl() function. Combined with the siteName variable,
you can generate links without hard‑coding your domain.
{# siteUrl and siteUrl() #}
<a href="{{ siteUrl }}">{{ siteName }}</a>
{# <a href="https://www.example.com">Example Site Name</a> #}
{{ siteUrl('contact') }}
{# https://www.example.com/contact #}The
{{ currentSite }} object and
craft.app.sites service
When a single Craft install powers
multiple websites
, it’s handy to inspect the current site and work with the full sites service.
{# currentSite and craft.app.sites #}
{{ currentSite.name }}
{{ currentSite.handle }}
{{ currentSite.id }}
{{ currentSite.group }}
{% set showBanner = currentSite.handle in ['site1', 'site2'] %}
{% if showBanner %}
...banner html here...
{% endif %}
{{ craft.app.sites.allSites }}
{{ craft.app.sites.primarySite }}
{{ craft.app.sites.totalSites }}Multi‑site setups are their own topic, but as a rule of thumb:
Site Groups
represent separate entities (for example, “ABC Widget Co.” and “Widget Co Foundation”), while individual Sites are often localizations within a group.
craft.app.request
Segments of the current URL, GET/POST data, and more can be accessed with the request object.
{# Access segments of https://www.example.com/about/team/jane #}
{{ craft.app.request.getSegments() }}
{{ craft.app.request.getSegment(1) }}
{# See if this is a POST request #}
{{ craft.app.request.isPost() }}
{# Access POST data #}
{{ craft.app.request.getBodyParam('inputname') }}Manipulating HTML
These functions and tags streamline HTML output, making it easy to create elements and set attribute values in a readable way:
tag()
The tag() function is great for outputting HTML elements and attributes using object‑style
syntax, including custom elements.
{{ tag('p', {
class: 'loud',
text: 'Twig is amazing!'
}) }}
{# <p class="loud">Twig is amazing!</p> #}
{{ tag('img', {
src: url('images/fish-images/flounder.jpg'),
class: 'block',
alt: 'flounder fish',
width: '400',
height: '400',
loading: 'lazy'
}) }}You can also use opening and closing % tag % tags to customize inner HTML:
{% tag 'a' with { class: 'btn' } %}
{# ...your html here, add a <span> or <svg>... #}
{% endtag %}attr()
and
url()The attr() function (and its
corresponding filter
) lets you compose attributes with object‑style syntax while HTML‑encoding values for you. The
url() function generates URLs without hard‑coding your domain and respects Craft's
trailing‑slash setting
.
{# Compose attributes safely #}
<button {{ attr({ title: 'Save & Continue' }) }}>Save</button>
{# <button title="Save & Continue">Save</button> #}
{# Generate URLs that respect Craft's settings #}
{{ url('services/consulting') }}
{# https://www.example.com/services/consulting #}% html %
,
% css % and
% js %These tags can be used to add arbitrary HTML, styles, and scripts to specific places in your template—such as the head, the beginning of the body, or just before the closing body tag.
{% html at endBody %}
<p>This paragraph will be placed before the closing body tag.</p>
{% endhtml %}
{% css %}
body {
--text-color: {{ entry.textColor }};
}
{% endcss %}
{% js "/assets/legacy-scripts/plugin.js" %}
{% js %}
console.log('Twig is cool');
{% endjs %}Functions for working with images
On top of tag(), Twig and Craft provide helpers for working with SVGs and tiny inline images
like
svg() and
dataUrl(). The latter lets you
embed tiny images as data URLs
directly in your markup.
{# Output SVG markup and add attributes with filters #}
{{ svg('@webroot/assets/example.svg')
|attr({ class: 'block w-4 h-4', role: 'img' })
|append('<title>Custom Title</title>', 'replace') }}
{# Embed a tiny image as a data URL #}
<img src="{{ dataUrl('@webroot/assets/img/my-tiny-asset.png') }}" alt="Tiny example image embedded as a data URL">Frequent filters
Filters modify variables—formatting dates, trimming whitespace, joining arrays, and more. Twig core and Craft both add
a stack of useful filters
; here are a few favorites.
|default
,
|date and
|date_modify{# Provide a default when a variable is empty #}
{{ card.text|default('No summary available...') }}
{# Format and modify dates #}
{{ now|date('Y') }}
Yesterday's date was: {{ now|date_modify('-1 day')|date('m/d/Y') }}|join
,
|filter and
|groupWe often use these to combine arrays of classes into readable strings—especially when working with
Tailwind CSS
, or to wrangle Matrix and Neo blocks into the exact shapes we need.
{# Join an array of classes into a single string #}
{% set classes = [
'block',
'py-12 px-2',
'border-2 border-white',
'text-center text-white',
'leading-none',
'no-underline uppercase tracking-wide'
] %}
{% for page in navigation %}
<li class="w-1/2 p-1">
<a class="{{ classes|join(' ') }}" href="{{ page.url }}">{{ page.title }}</a>
</li>
{% endfor %}
{# Filter and group Matrix blocks #}
{% set matrixField = globalSection.matrixField.all() %}
{% for block in matrixField|filter(b => b.type.handle == 'imageBlock') %}
{# ...output image blocks... #}
{% endfor %}
{% set grouped = matrixField|group(block => block.type) %}
{% if grouped|length %}
{% for type, blocksByType in grouped %}
{% if type == 'text' %}
{% for item in blocksByType %}
{# ...output text blocks... #}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}|escape
When you aren’t using attr(), you can still escape characters in attribute values with the
escape filter.
<img class="" src="{{ logo.url() }}" alt="{{ title|escape('html_attr') }}">
{# Try |escape('js') or |escape('url') as well. #}Templating tips
Here are a few “Did you know Twig can…?” tips that help keep templates clean and DRY (Don’t Repeat Yourself).
String interpolation
{% set greeting = "Hello, #{firstName|capitalize}." %}
{# instead of 'Hello, ' ~ firstName ~ '.' #}% for %}…{% else %}…{% endfor %}
and
% switch %}{% case %}For loops in Twig can include an % else % clause, and Craft adds a % switch %
tag that often reads cleaner than long chains of % if % and % else %.
<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% else %}
<li><em>no user found</em></li>
{% endfor %}
</ul>
{% switch block.type.handle %}
{% case 'wysiwyg' %}
{# Output a text block... #}
{% case 'image' %}
{# Output an image block... #}
{% default %}
<p>No matching blocks found.</p>
{% endswitch %}% set % blocks
% set % blocksfor chunks of HTML
This is a great way to keep your conditionals DRY.
{% set content %}
<span>I might need to be wrapped in a link, but only if the link exists...</span>
{% endset %}
{% if link %}
<a href="#">
{{ content }}
</a>
{% else %}
{{ content }}
{% endif %}Calling parent blocks and merging defaults
When overriding a block you can call parent()—which is part of Twig's
template inheritance
—and you can preserve defaults when including components by using Craft's
merge filter
. Credit for this merge pattern goes to
Andrew Welch
.
{# In /templates/base.twig #}
{% block sidebar %}
<div>
<h2>Follow Us</h2>
<p><a href="#">On Social Media</a></p>
</div>
{% endblock %}
{# In /templates/page.twig #}
{% extends "base.twig" %}
{% block sidebar %}
<h2>Sidebar Links</h2>
...
{{ parent() }} {# outputs the “Follow Us” block above #}
{% endblock %}
{# Preserve defaults and merge include parameters #}
{# page.twig #}
{% include 'component.twig', {
params: {
title: '',
description: '',
imageSrc: ''
}
} only %}
{# component.twig #}
{% set vars = {
title: 'Default Title',
description: 'Default description text ....',
imageSrc: '/assets/images/default.jpg'
} | merge(params) %}
{# ...the rest of your component code here. #}Utilities for debugging
In development, you’ll often want to inspect data or see what a variable contains—think print_r
in PHP or console.log in JavaScript. In Twig, we usually reach for
dd and
dump.
{# dd: "dump and die" from Craft #}
{% dd(variableName) %}
{# Or see everything in the current context #}
{% dd _context %}
{# dump: Twig core function that continues execution #}
{{ dump(entry, category) }}
<pre>
{{ dump(myTwigVar) }}
</pre>Be sure to enable Craft’s
devMode config setting
to use dd and dump. While you’re experimenting, we also recommend the
Template Comments plugin
by Andrew Welch—it makes it easier to track down which template each piece of HTML came from.
That’s it—you’ve reached the end of our quick reference list of TwigBits. We hope you found at least one helpful snippet in the mix. Did we miss one of your favorite bits of Twig? Let us know, and we may add it to the list.