Skip to content

Instantly share code, notes, and snippets.

@brandonkelly
Last active March 11, 2025 21:41
Show Gist options
  • Save brandonkelly/8584132 to your computer and use it in GitHub Desktop.
Save brandonkelly/8584132 to your computer and use it in GitHub Desktop.
Templating in EE vs. Craft

Templating in EE vs. Craft

Lots of people have asked, so here are a few common tasks you might do in your templates, as they would be written in ExpressionEngine vs. Craft.

Conditionals

ExpressionEngine

{if something == 'value'}
    ...
{if:elseif something_else == 'other_value'}
    ...
{if:else}
    ...
{/if}

Craft

{% if something == 'value' %}
    ...
{% elseif somethingElse == 'otherValue' %}
    ...
{% else %}
    ...
{% endif %}

See Twig’s {% if %} docs for more info.

Loops

ExpressionEngine

EE loops are always done with a tag pair, named after what we’re looping through.

{exp:channel:entries channel="news"}
    {matrix_field}
        ...
    {/matrix_field}
{/exp:channel:entries}

Craft

Looping through things in Craft is always done with a for-loop, where you pass in what you want to loop through, as well as what you want to call each item within the loop.

{% for entry in craft.entries.channel('news') %}
    {% for block in entry.matrixField %}
        ...
    {% endfor %}
{% endfor %}

There are pros and cons to both ways: EE’s is more elegant for simple things, but you can quickly run into tag name conflicts and other gotcha’s when dealing with more complex templates. That’s never an issue with Craft, but it’s at the expense of a more verbose and less straightforward syntax.

Both CMSes have various tags that are available within the loops, as well:

Thing ExpressionEngine Craft
How many items? {total_results} {{ loop.length }}
1-based index {count} {{ loop.index }}
0-based index SOL {{ loop.index0 }}
How many items are left? SOL {{ loop.revindex0 }}
How many items are left, including this one? SOL {{ loop.revindex }}
First item? {if count == 1} {% if loop.first %}
Last item? {if count == total_results} {% if loop.last %}
Odd? {if count % 2 == 1} {% if loop.odd %}
Even? {if count % 2 == 0} {% if loop.even %}
What’s the parent’s index (in a nested loop)? SOL {{ loop.parent.loop.index }}

See Twig’s {% for %} docs for more info.

Looping through entries

ExpressionEngine

ExpressionEngine’s {exp:channel:entries} tag is optimized for this task (assuming you’re not on a single-entry page!). Just tell it which channel, how many, and so on.

{exp:channel:entries channel="news" limit="10"}
    <h2><a href="{path='news/{url_title}'}">{title}</a></h2>
    <p>{summary}</p>
{/exp:channel:entries}

Craft

In Craft you grab the entries using craft.entries and loop through them with a for-loop. You get to choose a variable name that each entry is going to be set to. In this case we’re going with newsEntry so it’s clear which ‘title’ we’re outputting, etc..

{% for newsEntry in craft.entries.section('news').limit(10) %}
    <h2><a href="{{ newsEntry.url }}">{{ newsEntry.title }}</a></h2>
    <p>{{ newsEntry.summary }}</p>
{% endfor %}

See EntryModel to see what you can do with those entry variables.

Outputting an entry on a single-entry page

ExpressionEngine

Assuming everything’s in the right place, this is the one time the dynamic parameter will actually help you out, saving you from having to type url_title="{segment_2}" limit="1" (Whew!).

{exp:channel:entries channel="news"}
    <h1>{title}</h1>
    {body}
{/exp:channel:entries}

Craft

Entries in Craft have their own URLs, so Craft knows for a fact which entry you’re trying to access, and which template it should load. In the process it will pass an entry variable, pre-set to the entry you’re accessing. (In this case you don’t get a choice on what the entry variable name is going to be called.)

<h1>{{ entry.title }}</h1>
{{ entry.body }}

Looping through Matrix rows

ExpressionEngine

Matrix follows the standard EE fieldtype convention of parsing a tag pair based on the field’s short name:

{matrix_field}
    {column_one}
    {column_two}
{/matrix_field}

Craft

As with looping through entries, we loop through Matrix blocks using a for-loop.

{% for block in entry.matrixField %}
    {{ block.fieldOne }}
    {{ block.fieldTwo }}
{% endfor %}

If you have multiple block types, you can add conditionals for them:

{% for block in entry.matrixField %}
    {% if block.type == "text" %}

        {{ block.textField }}

    {% elseif block.type == "quote" %}

        <blockquote>{{ block.quoteField }}</blockquote>
        <p>– {{ block.authorField }}</p>

    {% endif %}
{% endfor %}

See MatrixBlockModel to see what you can do with those Matrix block variables.

Looping through assets

ExpressionEngine

Like Matrix/EE, Assets uses a tag pair based on the field’s short name:

{assets_field}
     <img src="{url:image_manipulation_name}" alt="{title}"> {filename}
{/assets_field}

Craft

Like entries and Matrix fields in Craft, we once again use the for-loop to loop through assets:

{% for asset in entry.assetsField %}
    <img src="{{ asset.url('transformHandle') }}" alt="{{ asset.title }}"> {{ asset.filename }}
{% endfor %}

See AssetFileModel to see what you can do with those asset variables.

DRY site header and footer

ExpressionEngine

In EE you would do this with two embedded templates, which you’d manually include in every normal template.

includes/_header

<html>
<head>
    <title>{if embed:page_name}{embed:page_name} - {/if}{site_name}</title>
</head>
<body>

includes/_footer

    <p class="copyright">
        &copy; {current_time format='%Y'} {site_name}
    </p>
</body>
</html>

news/index

{embed="includes/_header" page_name="News"}
    <h1>News</h1>
    ...
{embed="includes/_footer"}

Craft

Craft has the concept of Template Inheritance, which lets you define all of the common site elements in a single template, and extend it with sub-templates where necessary.

_site_layout.html

<html>
<head>
    <title>{% if pageName is defined %}{{ pageName }} - {% endif %}{{ siteName }}</title>
</head>
<body>

    {% block body %}
        Default content
    {% endblock %}
    
    <p class="copyright">
        &copy; {{ now | date('Y') }} {{ siteName }}
    </p>
</body>
</html>

news/index.html

{% extends "_site_layout" %}
{% set pageName = "News" %}

{% block body %}
    <h1>News</h1>
    ...
{% endblock %}
@lukeholder
Copy link

this should be on the craft docs yeah?

@toddprouty
Copy link

Great write-up, and I agree it would fit well in the Craft docs. I fixed a few minor typos if you're interested.

@hybridinteractive
Copy link

There's an error under LOOPS:

{{ cycle(['odd','even'], loop.index0 }}

Should read:

{{ cycle(['odd','even'], loop.index0) }}

@philzelnar
Copy link

This is incredibly useful. In the Loops section Craft sample, I think craft.entries.channel('news') needs to be updated to craft.entries.section('news'), but I'll leave it to smarted folks to decide.

+10 for section 11.

@mcmadafly
Copy link

This is fantastic!

@knynkwl
Copy link

knynkwl commented Jun 15, 2015

This is Awesome! Thanks!

@BubbleJeff
Copy link

Way more informative for a beginner than the documentation of Craft itself. Thanks a bunch!

@mattman93
Copy link

Things you can only do in EE .. custom sql to retrieve entire entries or specific field data i.e. {exp:query sql="SELECT * FROM exp_channel_data WHERE field_id_10 = '0'"} {display data} {/exp:query}

@RickKukiela
Copy link

@mattman93 SQL queries have no place inside a template though.

@thewebprojects
Copy link

thewebprojects commented Aug 30, 2017

Great article. Very useful.

I haven't used Craft or EE so correct me if I'm wrong, but it looks like you can write PHP in EE templates. In Craft you can not write PHP in your templates. You are limited to what Twig provides. So if you enable PHP in a EE template you can do all the "stuff you can only do in Craft" and more (assuming you'll use PHP).

@gwarvi
Copy link

gwarvi commented Feb 26, 2018

Does Craft have a parameter like ExpressionEngine's dynamic?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment