Macros or Partials? When to use both for keeping Craft CMS templates clean.

Shawna O’Neal
Dodgy Code
Published in
8 min readJul 13, 2020

If you’re abstracting your code and creating modular components — GOOD! You’re in the right place, and we’re less likely to make fun of you.

If not… well, why aren’t you?

A macbook, with the VSCode editor running, sits on a white table with some vibrant pots of live plants.

Abstracting Your Templates

Abstraction is a key part of Object Oriented Programming (OOP), where complexity is broken down into segments (often called abstraction layers) in order to hide unnecessary information. The goal of abstraction is to keep only the relevant and necessary data within the purview of the logic at hand, while also providing enough human readibility to properly navigate the structure.

Think of abstraction like you would the back kitchen of your favourite burger joint. There isn’t one cook making a burger. There’s the grill cook, the burger assembler, the fry cook … each one responsible for one of several small tasks in the creation of your meal.

A couple of fresh burgers served in a red basket at a Louisiana bar. An alligator statue sits on the table with the burgers.

In this example, the fry cook doesn’t need to know how to grill, and the assembler doesn’t need to know if the order comes with fries — In the end, you still get your order, assembled correctly.

This is abstraction, and when done right it’s downright powerful in creating clean code and improving efficiency. We can take it a step further by implementing our abstraction layers to be modular, meaning they can be re-used across different tasks and problems. This concept, when applied to writing templates, follows the mantra of “Don’t Repeat Yourself” — commonly referred to as DRY Templating.

Macros v. Partials

The two common ways to abstract logic and components in Craft CMS templates uses Twig’s macro and partials features.

Macro

Macros are easily comparible to the functions we’d write in any standard programming language. As such, they have arguments that can be pre-defined with default values. All versions of Twig support {% macro %}.

Example code for defining macros, taken from Twig’s online documentation
https://twig.symfony.com/doc/3.x/tags/macro.html

By leveraging the modularity of macros, we can create functions for repetitive logic and simply call them as needed. This means predictable and consistent behaviour across multiple templates, and easier editibility when logic changes are implemented.

Where are macros defined in Craft CMS?

Macros can be defined both inside a template, or within an external Twig file. When defining a macro for use across multiple templates, the macro should always be defined in an external file. While I’m not particularly fond of defining single-use macros (it conflicts with the concept of clean modularity in code design) they can also be created within the template they’ll be used in.

To their credit, multiple macros can be defined in a single twig file. Creating macro sheets allows for easy grouping and searching of the code base, without any performance negatives.

Are default values required?

Default values for arguments are not required when creating a macro. That said, it’s good practice to program defensively and a default argument can provide assurance that “variable is not defined” errors don’t occur.

What can be used as macro arguments?

There isn’t a limit to what can be passed in a macro’s arguments. Objects, arrays, results from entry queries, matrix blocks … anything that can be assigned to a variable can be passed into a macro.

Are there any scoping concerns when using macros?

A defining feature of macros is their lack of access to template variables. All data used within a macro must be passed via argument (in Craft CMS, this does include the entry object). This may sound limiting, but macro usage creates a system where there’s never any redefining of variables, duplicated allocations, or mismatched data. The result is cleaner and more consistent than trying to pass data to a partial.

In addition to arguments, importing macros into a template does have it’s own scope concerns. From Twig’s documentation: “Imported macros are always local to the current template. It means that macros are available in all blocks and other macros defined in the current template, but they are not available in included templates or child templates; you need to explicitly re-import macros in each template.”

Macros need to be included at the top of templates. Additionally, if a macro is imported within a {% block %}, the definition of that macro is limited in scope to that block.

An article entry template where an embed is used in conjunction with a child block (menu). Despite already being defined (at the top), the ‘“Actions” macro must be defined again for this new scope.

Note: Macro scoping changes a wee bit in the course of upgrading from Craft 2 to Craft 3. Instead of invoking _self from within a macro when needing to reference a macro in the same file (or call the same macro recursively), the macro must now be defined as it would in any other context.

Craft 2:
{% macro foo %}...{% endmacro %}
{{ _self.foo() }}

Craft 3:
{% macro foo %}...{% endmacro %}
{% import _self as macros %}
{{ macros.foo() }}

Example: Macros for ‘Action Text’

In this example, four macros were created for calculating text and href values for article links based on a given article’s entry type. In all four cases, the macro’s argument is ‘resource’.

Note that the macro file includes docblocks. This ensures that other developers understand what the macro does, and what parameters it expects to have.

Excerpt from templates/resources/_detail.twig where two of the “Action” macros are called.

The macro is called within our template by invoking the desired macro on the variable we created to represent the imported file. In the detail template for our resources, this would be the _actions.determineResourceLink and _actions.createResourceAction calls. The “Determine Resource Link” macro returns a url. The “Create Resource Action” macro returns markup for the text that appears at the bottom of the card.

The “Action” macro sheet helps keep consistent wording an link formatting for the various types of articles and resources across the site. Additionally, changes to content or functionality are limited to one central location — creating less work and very little chance of deprecated code errors.

Partials

Partials are snippets of HTML that are imported into a Twig template using the {% include %} tag. All versions of Twig support partials.

An example of include tags, take from Twig’s online documentation.
https://twig.symfony.com/doc/2.x/tags/include.html

Partials include some additional parameters for fine tuning which files should be included into the template, in addition to which variables and definitions to share with the partial code.

Example of sharing variables within an include tag, and before the include is defined (showing the include has access to it)
Two ways of sharing variables to the partial.

Using the ‘only’ parameter will restrict access on the current context for the partial. The current context would include any variables or session data available on the calling template.

The best times to use this are when there’s variable naming collisions within a partial, or a performance concern. In general, I prefer using specific and consistent naming over restricting context access and I’ve never encountered a performance situation requiring the restriction of context to a partial. I’d strongly reconsider the programming architecture in-use if facing the ‘only’ parameter becomes a requirement in your template.

Examples of the ‘only’ parameter, both with variables and without. Taken from Twig’s online documentation.
Restricting partial access to specific data, or none of the current context.

What about extending templates?

Partials shouldn’t be confused with extending templates, where one template is imported into another using the Twig {% extends %} and {% block %} inheritance tags or by invoking the {% embed %} tag.

Extension is used to define parent layouts and child modification of those layouts, a vertical relationship, that creates specifics on a generic. When using {% extends %} the parent template is modified by making calls to its blocks on the child template.

Partials, on the other hand, are meant for code reuse. The code within a partial has no relationship with the template it is included within, meaning that changes within a partial’s code will not impact the parent template. Partials work more like a variable than a template, in that calling a partial via the {% include %} tag will simply pull in the content that defines the partial.

While both tag sets will technically ‘work’ in a functional sense, there are code design patterns to consider for creating truly modular and efficient templates.

Where are partials defined in Craft CMS?

Partials are always includes from an external template. The common practice is to create a folder called _partials within the templates directory. From there you can create twig files, or subfolders of twig files, to be called from other templates.

Did you know that the underscore in the directory name within templates will prevent Craft CMS from auto-routing the folder and it’s contents? If we had created templates/partials/partial.twig we’d have an (unwanted) live URL for https://www.domain.com/partials/partial.

Are there any scoping concerns when using Partials?

A partial has access to all of the variables that are scoped to the calling template, so long as the ‘only’ parameter wasn’t used. Be mindful that variables will be expected to match the naming used within the partial.

Example: Partial for ‘Contact Bar’

In this example, a section featuring three actions (each opening a modal) is defined in the partial file. Once defined, it can then be called across templates or within macros. Updating the code can be done in one place, with the effect occuring sitewide and without mismatching edits.

templates/_partials/contactBarBlock.twig
Excerpt from templates/solutions/_entry.twig where the contactBarBlock partial is called.

Example: Partial for ‘Popular Now’

In this example, a partial is used for displaying the most frequently visited articles for a given section of the site. The partial is included in a template that already extends the main layout template, and provides the complete markup for the “Popular Now” section.

Note that the partial is passed the articles variable, which holds the value taken from the popularArticles definition on the template. This technique accomodates for the partial expecting an articles variable, which has not been defined on the template.

templates/_components/_popular-now.twig
Excerpt from templates/states/_overview.twig where the popular-now partial is called.

A case can be made that the “Popular Now” code could have been a macro instead of a partial. Because the partial is singular (having no variations that would benefit from being placed in the same macro sheet), and has a very limited set of passed parameters, it’s a good candidate for a partial. That said, a macro isn’t wrong either.

This example is definitely a bit of a grey area, and I’d encourage any developer to do what makes the most sense for their project to ensure maintainability and stability in their code base.

So… Who Wins?

Truth is, there was never any competition to begin with. Macros and partials are both useful Twig features that work extremely well within Craft CMS — the trick is using either appropriately. As always, you should do whatever makes your code clean, reusable, and maintainable.

--

--

Shawna O’Neal
Dodgy Code

Longtime Craft CMS developer and Clean Code practitioner. Analogy afficianado. Contents may include salt.