{{craftSnippets}}
Home Articles Plugins Starter theme About
  • Home
  • Articles
  • Using Javascript in Twig templates with Craft CMS
Posted on Oct 13, 2019 by Piotr Pogorzelski

Using Javascript in Twig templates with Craft CMS

Learn how to use Javascript in Twig templates in order to make your code more portable and maintainable.

Single file components in Twig #

Why would we want to use Javascript code in templates, alongside HTML - instead of storing it in separate files? What about layer separation? Vue JS framework popularized concept of single file components - code living alongside javascript (and CSS) in one file.

Let's think of the image gallery component. Such component contains HTML markup, some Twig logic, like for loop and image transforms. Then, there is a Javascript function call which is responsible for lightbox. If we store all of that in one file we gain some advantages:

  • Twig and JS code is more tightly knit. You can easily transfer Twig variables into JS.
  • Your code is more portable - you can easily reuse component by just copying a single file to other project.
  • When you want to tweak something, you don't need to look for code in two separate places - Javascript files and Twig template - but only at one. Thanks to that, code is easier to maintain.

There are also disadvantages - the main one is that using Javascript directly in Twig takes away your ability to use preprocessors like Babel. This means that if you want to support legacy browsers, you will not be able to use ES6.

js Twig tag #

js Twig tag is essential to using JS in Twig. It takes JS code passed to it and appends it at end of the template, right before ending body tag. Multiple js tags can be used - content from each of them will be concated with the rest. You don't need to wrap code passed to js into <script> tags - js will do that for you.

js tag has one limitation - it cannot be used inside {% cache %} tags. Caching specific parts of template saves HTML content generated by them to database - so Twig code does not need to run. This however stops js tag from working.

Your Twig component doesn't need to rely only on inline code - if you want to include external JS file from within Twig, you can use registerJsFile function. Link to this file will also be included at the end of the template.

{% do view.registerJsFile('directory/some-file.js') %}

There is also CSS equivalent of js - css tag. It works pretty much the same as js, except it puts its contents into <head> section of the website. I don't really use it - there would be problems with CSS preprocessors like gulp.

Passing data from Twig to JS #

Now that your JS code lives alongside Twig, you can easily transfer Twig variables into Javascript. For example:

alert({{twig_variable|json_encode}});

From Twig point of view, JS code is just text string - that's why we can just concate Twig variables with text that make up JS code. json_encode is used to transform Twig variables into a form that can be consumed by JS. Thanks to that, Twig true will turn into JS true, not 1 - which would happen if we just outputted {{true}} into the template.

Such approach will work, but it's pretty messy to mix Twig and JS like this - it can be hard to recognize on first glance which parts of code is Twig and which is JS. It is better to define JS variables containing data from Twig before JS code starts. You can do that using Twig macro:

{% macro jsVar(variable, jsVariableName) %}
var {{jsVariableName}} = {{variable|json_encode|raw}};
{% endmacro %}

This macro takes two arguments:

  • variable - twig variable or object that needs to be transferred to JS.
  • jsVariableName - JS variable name that will have assigned Twig variable value.

With jsVar macro, our example with alert will look like this:

{{_self.jsVar(twig_variable, 'javascript_variable_name')}}
alert(javascript_variable_name);

Which will render this code - assuming that twig_variable contains string "abc":

var javascript_variable_name = 'abc';
alert(javascript_variable_name);

Javascript helpers plugin #

Javascript helpers is small plugin that I created to make working with JS in Twig easier. It's main feature is transfering all static message translations into javascript array. Normally, you would need to define each message manually, like this:

var message_1 = {{'message_1'|t|json_encode}};
var message_2 = {{'message_2'|t|json_encode}};

With Javascript helpers plugin you can transfer all static message translations at once:

{{craft.jsHelpers.outputMessages('all_messages')}}

Parameter passed to function is name of array. Assuming that you have only two static message translations, rendered result will look like this:

var all_messages = {message_1: 'something 1', message_2: 'something 2'};

You can also use plugin to transfer Twig variables into Javascript by using Twig filter instead of macro:

{{some_twig_variable|jsVar('js_variable_name')}}

Component namespacing #

Some components containing JS code can be used in multiple parts of the website at the same time. Let's think on carousel component. It renders slides using for loop. Carousel script uses carouselSpeed setting which takes value from Twig carousel.speed variable.

{% is carousel is defined %}
	<ul class="js-carousel">
	{% for slide in carousel.slides %}
	<li class="single-slide">
		<img src="{{slide.url}}" alt="">
	</li>
	{% endfor %}
	</ul>

	{% js %}
	{{_self.jsVar(carousel.speed, 'speed')}}
	$('.js-carousel').initialiseCarousel({
		carouselSpeed: speed
	})
	{% endjs %}
{% endif %}

As you can see, we have initialized carousel script on the element with js-carousel class. What if the same carousel component is used simultaneously somewhere else on a currently displayed page? It might display different images, have different carousel.speed setting, but its class attribute stays identical. This means that initializing script with specific settings would affect two carousels at once.

To avoid that, elements in Twig components affected by Javascript should have their classes (or other attributes used by JS) namespaced. Let's add namespace variable to carousel example:

{% is carousel is defined %}
	<ul class="js-carousel{{namespace ?? null}}">
	{% for slide in carousel.slides %}
	<li class="single-slide">
		<img src="{{slide.url}}" alt="">
	</li>
	{% endfor %}
	</ul>

	{% js %}
	{{_self.jsVar(namespace ?? null, 'carousel_namespace')}}
	{{_self.jsVar(carousel.speed, 'speed')}}
	$('.js-carousel'+carousel_namespace).initialiseCarousel({
		carouselSpeed: speed
	})
	{% endjs %}
{% endif %}

Namespace should be passed into the component using with keyword. You should also use only keyword, to ensure that component is isolated from the variable context of its parent file - which might also has its own namespace.

{% include 'carousel_component' with {namespace: '-some-carousel'} only %}

If you use your component in for loop, you can use loop.index or element id as a part of namespace variable:

{% for block in entry.matrixField %}
	{% include 'carousel_component' with {namespace: '-some-carousel-'~block.id, block: block} only %}
{% endfor %}

What about js- prefix that carousel class has? Using that prefix reminds us that this class is used as a javascript hook and NOT for styling. Thanks to that, styling and javascript functionality are decoupled and changing javascript related classes wont break any styling. It's really good convention to follow.


TAGS:
#Craft CMS 3 #Twig templating #Twig macro
Table of contents:
  • Single file components in Twig
  • js Twig tag
  • Passing data from Twig to JS
  • Javascript helpers plugin
  • Component namespacing
Quick links for this article:
Macro for passing Twig variables into Javascript on github gists.
Let the expert handle
your Craft CMS project.
70 Basic icons by Xicons.co
piotrpog@protonmail.com
Copyright © 2026 Piotr Pogorzelski
piotrpog@protonmail.com      LinkedIn profile      Github profile