Skip to Main Content
Let's Talk

Twig is the templating language used by Craft CMS to render web pages. We use Twig every day to do things like:

  • List recent blog posts
  • Output dynamic content
  • Output forms or features from popular Craft plugins
  • And create 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. We’re regularly delighted by just how easy it is to build templates with Twig in Craft.

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. Enjoy!

Reading Craft's Config Settings

All of Craft's environment variables and General Config Settings can be easily 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() }} function and the {{ craft.app.config.general }} service. Here are a few examples:

{# 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 %} {# # or access a custom config property you've added #} {# # 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() %}

Craft 4 custom config settings are placed in a separate file, while in Craft 3 they can be added alongside the general config settings. You may also want to check out 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 literally dozens of times when building a Craft site.

{{ siteUrl }} variable/function and {{ siteName }} variable

As it says on the tin, the siteUrl variable outputs the url of the current site. But note that there is also a siteUrl function as well. Along with the {{ siteName }} variable, you could write the following:

<a href="{{ siteUrl }}">{{ siteName }}</a> {# output looks like <a href="https://www.example.com">Example Site Name</a> #} {{ siteUrl('contact') }} {# outputs a link to your site like https://www.example.com/contact #}

The {{ currentSite }} object and {{ craft.app.sites }} service

We often use Craft CMS to power multiple websites from a single installation. In that context, it's convenient to access information about both the current site and any other sites via the sites service.

{# The current site's properties include #} {{ currentSite.name }} {{ currentSite.handle }} {{ currentSite.id }} {{ currentSite.group }} {# You could use this to conditionally show or hide content per site as in... #} {% set showBanner = currentSite.handle in ['site1', 'site2'] %} {% if showBanner %} ...banner html here {% endif %} {# All sites can be accessed with the craft.app.sites service #} {{ craft.app.sites.allSites }} {{ craft.app.sites.primarySite }} {{ craft.app.sites.totalSites }}

Multi-sites in Craft CMS is a topic unto itself, but it's worth noting a couple things here:

  • Each installation of Craft has only one Primary Site that serves as the default.
  • Site Groups are generally used for separate entities (ex. separate site groups for "ABC Widget Co." and the "Widget Co Foundation").
  • While Sites are used for localizations (translations) of the same entity within a Site Group.

So, for example, in a Site Group called "Widget Co Foundation" you may have three sites including Widget Co Foundation English, Widget Co Foundation Spanish, and Widget Co Foundation German.

The {{ craft.app.request }} object

Segments of the current url, GET / POST data, and more can be accessed with the request object.

{# Access segment(s) of a url such as 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:

{{ tag() }}

The tag function is super convenient for outputting an element and its attributes using object-style syntax. It can even be used for custom HTML elements.

{{ tag('p', { class: 'loud', text: 'Twig is amazing!' }) }} {# Outputs: <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' }) }} {# Output an image! #} {{ tag( 'x-svelte', { component: 'brand-story', } ) }} {# Outputs: <x-svelte component="brand-story"></x-svelte> #}

{{ attr() }}

With this function (and corresponding filter) you can compose an element's attributes with object-style syntax while outputting the tag itself manually. Another benefit of using this function versus creating attributes manually is that characters in attribute values, such as "&", will be HTML-encoded for you automatically.

<button {{ attr({ title: 'Save & Continue' }) }}>... {# Output: <button title="Save &amp; Continue">... #}

{{ url() }}

The url function lets you avoid hard-coding domains or links into your templates. It is essentially the same as the siteUrl function mentioned previously, but it can also be used to generate urls to sites other than just yours. If you require a trailing slash at the end of every url for SEO purposes, this function will respect Craft's setting for that as well.

{{ url('services/consulting') }} {# output looks like https://www.example.com/services/consulting #}

The {% html %}, {% css %}, and {% js %} tags

These three similar tags can be used to add arbitrary scripts, styles, and html code to specific places in your template. Whether you need to add a style tag in the head or include a modal element just before the closing body tag, these tags are your best friends.

{# Try them with 'at head', 'at beginBody', or 'at endBody' #} {% html at endBody %} <p>This paragraph will be placed before the closing body tag.</p> {% endhtml %} {# Define a css custom property based an entry's field #} {% css %} body { --text-color: {{ entry.textColor }}; } {% endcss %} {# Include a single javascript file in a script tag #} {% js "/assets/legacy-scripts/plugin.js" %} {# Or include javascript code between the opening and closing tags #} {% js %} console.log('Twig is cool'); {% endjs %}

Functions for Working with Images

We've seen how image tags can be created using the tag() function already, plus these two functions provide a convenient way to work with svgs and data urls.

{{ svg() }}

This function outputs svg code directly into your template. See Craft's documentation for the parameters and additional examples; it's very versatile.

{# attributes and content can be added with filters #} {{ svg('@webroot/assets/example.svg')|attr({class:'block w-4 h-4', role:'img'})|append('<title>Custom Title</title>', 'replace') }}

{{ dataurl() }}

This function allows you to embed tiny images inline in the document. Most commonly, this would be used for small icons or css background images.

{# using a file path to generate a base64-encoded data url #} <img src="{{ dataUrl('@webroot/assets/img/my-tiny-asset.png') }}">

Frequent Filters

Filters are used to modify variables—think tasks like formatting a date or trimming the white space from the ends of a string. Both Twig core and Craft offer a stack of useful filters. It would be worth your time to familiarize yourself with all of them, but here are a few of our favorites:

{{ ...|default() }}

To output default text when a variable is not defined or empty.

{# you an avoid using an if conditional and output a default value #} {{ card.text|default('No summary available...') }}

{{ ...|date() }} and {{ ...|date_modify() }}

To format and modify dates.

{# use with the 'now' global variable and a PHP date string #} {{ now|date('Y') }} {# like php's strtotime function #} Yesterday's date was: {{ now|date_modify('-1 day')|date("m/d/Y") }}

{{ ...|join() }}

To combine an array of items into a string. We use this frequently to make our class lists easy to digest at a glance. (P.S. as you can see, we also love Tailwind CSS.)

{# handy to visually group and sort classes #} {% 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() }}

To work with arrays and objects. We use Matrix fields and the Neo plugin on most projects and we leverage these two filters to control their output.

{# |filter() example: loop over blocks of a certain 'type' only #} {% set matrixField = globalSection.matrixField.all() %} {% for block in matrixField|filter(b => b.type.handle == 'imageBlock') %} ... {% endfor %} {# |group() example: group matrix blocks by their 'type' #} {% set matrixField = globalSection.matrixField.all() %} {% set groupedMatrixField = matrixField|group(block => block.type) %} {% if groupedMatrixField|length %} {% for type, blocksByType in groupedMatrixField %} {% if type == 'text' %} {% for item in blocksByType %} ... {% endfor %} {% endif %} {% endfor %} {% endif %}

{{...|escape() }}

To escape characters in HTML. If you are not using the attr() function mentioned earlier, you can escape special characters in attribute values with this filter.

<img class="" src="{{ logo.url() }}" alt="{{ title|escape('html_attr') }}"> {# try |escape('js'), |escape('url') too! #}

Templating Tips

Here are a few "Did you know Twig can...?" tips that will help you create clean, DRY (Don't Repeat Yourself) templates.

You can interpolate strings.

Yes, you can interpolate strings in Twig! So instead of concatenating with the tilde character "~", you could do the following:

{% set greeting = "Hello, #{firstName|capitalize}." %} {# instead of 'Hello, ' ~ firsName ~ '.' #}

For loops can have an {% else %} statement.

Who knew!

<ul> {% for user in users %} <li>{{ user.username }}</li> {% else %} <li><em>no user found</em></li> {% endfor %} </ul>

You can {% set %} a chunk 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="/path/to/somewhere"> {{ content }} </a> {% else %} {{ content }} {% endif %}

You can call a parent block from a child block.

When overriding a block, it's possible to render the contents of the parent block by using Twig's built-in parent() function. (Template inheritance is powerful.)

{# 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() }} {# should output your 'Follow Us' code above #} {% endblock %}

You can preserve defaults and merge parameters with your includes.

This works for nested arrays and object (hashes) too by adding true as the recursive param (see Craft's docs for merge). Credit goes to Andrew Welch for this tip.

{# 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 will want to inspect some data, see what the value of a variable is, or output an array to view the items it contains—think print_r in PHP or console.log in JS. In Twig we accomplish this with dd and dump.

{% dd() %}

The dd tag is added by Craft; and, it literally means "dump and die." This tag will output a variable to the screen and stop further execution.

{% dd(variableName) %} {# or see everything in the current context #} {% dd _context %}

{{ dump() }}

The dump function is part of Twig core. It's not as heavy-handed as Craft's dd, and simply outputs a variable inline in your HTML and continues executing the template.

{{ dump(entry, category) }} {# or wrap in a <pre> tag #} <pre> {{ dump(myTwigVar) }} </pre>

Be sure to enable Craft's devMode config setting to enable dd and dump. While we're speaking of debugging, check out the Template Comments plugin by Andrew Welch. It makes it easy to track down which template file content originated 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.

Think Craft CMS would be a good fit for your project?

We do too.