We use cookies to make your viewing experience better. By accepting you consent, you agree to our Cookie policy

Improve your Craft CMS skills

Where Does Theme Logic Live In Craft CMS Sites?

10 min read
Shape April 2022 HR 28

Crafting compelling experiences with Craft CMS requires mastering its Theme Logic. This guide provides invaluable insights into the anatomy of Theme Logic across templates, modules, services, and plugins. Learn precisely where logic lives in Craft and how to wield it for dynamic interfaces. Discover advanced techniques for performance, testing, and debugging to level up your custom logic skills.

Theme Logic in Craft CMS resides primarily in Twig templates, where variables, tags, functions, and other code controls how templates render content on the frontend. Logic can also live in template hooks, plugins, modules, global config files, and other locations that modify templates and content output.

Theme Logic in Craft CMS

Defining Theme Logic

Theme Logic refers to the programming logic and code that controls how templates are displayed in a Craft CMS website. It is the presentation layer that determines what and how content is rendered on the frontend of a Craft site built with Twig templates.

In essence, Theme Logic is the display and presentation logic that handles taking content from the CMS and outputting it with the proper HTML/CSS styling, dynamic elements, and desired structure. It controls the look, feel, and functionality of the frontend experience.

Theme Logic in Craft CMS lives primarily in Twig templates, where variables, tags, functions, and other logic can modify how the underlying content and data are displayed. Beyond Twig, logic can also live in template hooks, plugins, modules, and global configuration files. But the bulk of it resides within the .twig template files themselves.

Theme Logic enables developers to take static content from Craft and display it dynamically on the frontend. It's what takes plain text and images from the CMS and formats them beautifully on the live site. Things like gallery grids, interactive menus, hero sections, and more are all possible because of Theme Logic added to Twig templates.

So in summary, Theme Logic refers to all the presentation and display code that controls how templates and content are rendered for visitors. It's a core part of building Craft CMS websites, allowing for robust frontend experiences far beyond just static HTML output.

Types of Theme Logic

There are a few primary locations where Theme Logic lives within a Craft CMS project:

Twig Templates

The bulk of Theme Logic resides within .twig template files. This is where variables, tags, functions, and other code controls how the templates render content on the frontend. Things like page layouts, includes, macros, filters, conditional logic, loops, and more happen in Twig.

Template Hooks

Template hooks allow logic to be injected into templates without editing the original files. They are a common way for plugins and modules to integrate extra logic and functionality into existing templates.


Plugins can contain their own Theme Logic to add new Twig variables, functions, filters, etc. This allows them to modify templates and content output globally.


Frontend-display focused modules like template hooks, template caches, etc. enable logic reuse across different templates.

Config Files

The config/general.php file contains global logic like environment variables, aliases, defines, etc.

So in essence, any files that modify templates, provide display logic/helpers, or alter content output could contain relevant Theme Logic in a Craft site. But Twig templates are the main place it lives.

Role of Theme Logic

The overall role of Theme Logic in Craft is controlling how templates and content are displayed in the frontend presentation layer. It enables developers to take the templates, content structures, and data from Craft CMS and display them beautifully and dynamically on the live site.

More specifically, Theme Logic:

  • Modifies and augments the default output of CMS content with custom display logic

  • Allows for robust, interactive interfaces and experiences using CMS content as a base

  • Provides dynamic functionality like galleries, menus, interactive elements, etc.

  • Controls layouts, styling, and structure of templates

  • Handles formatting images, text, media, etc. for presentation

  • Renders different experiences across devices and screen sizes

  • Enables personalization, segmenting, A/B testing of experiences

  • Pulls in related content, assets, etc. for richer pages

  • Allows for conditional display logic, helper functions, and more

Without Theme Logic, Craft templates would just output plain HTML in the most basic way.

Theme Logic enhances and controls that output to create rich, dynamic, conversion-focused frontend experiences. It plays an absolutely vital role in taking Craft CMS from a content management backend to a fully-functional live website.

In summary, Theme Logic controls the presentation layer and handles displaying content in Craft sites. It's what enables the flexible, robust experiences that makes Craft CMS so powerful for creating compelling websites. Theme Logic brings templates and content to life.

Twig Templating and Theme Logic

Twig Templates in Craft CMS

Twig is the templating language used in Craft CMS for displaying content and data on the frontend. Twig templates control how all the content, assets, and data stored in Craft gets rendered on the live site.

Some common Twig template files used in Craft projects include:

  • index.html - The main template that displays pages, entries, etc.

  • _layouts/ - Layout templates that provide the scaffolding for other templates.

  • _includes/ - Reusable template partials, macros, etc.

  • _macros/ - Commonly used Twig macros.

  • _mixins/ - Global available Twig mixins.

Twig templates consist of a mix of regular HTML markup, along with special Twig syntax like variables, functions, filters, tags, and more. This Twig code injects dynamic content and logic into the template files.

For example, a blog post template might pull in the entry variable to display the dynamic post content, title, author, etc. Or a photo gallery template could use a for loop and asset variables to output images.

Twig enables non-developers to build templates in Craft while allowing developers to inject logic and programming to control presentation and output. It bridges the gap between content editors using the CMS and programmers developing the frontend display and functionality.

Template Variables

Template variables are a core concept in Twig templating for Craft CMS. They allow templates to dynamically pull in content, data, assets, and more from the CMS.

Some common template variables include:

  • craft.app - The global Craft app instance object.

  • entry - An entry's content, fields, etc.

  • category - Category data like title, slug, etc.

  • currentUser - Details on the currently logged in front- or backend user.

  • myCustomVariable - Any custom variable created and passed to a template.

Developers can use Twig's set tag to create new template variables. And variables get passed from controllers to templates automatically, like the entry variable on an entry template.

Template variables enable templates to output dynamic, personalized content by tapping into Craft's content model and database. They are what allow the presentation layer to render various experiences using one set of templates

For example, the entry variable on a blog template might display a completely different post each time the template is rendered. And a category variable can filter and modify content output based on the category viewed.

Theme Logic Using Twig

Some examples of common Theme Logic used in Twig templates includes:


Conditionals like if, elseif, else allow for selective, segmented output:

{% if entry.coverPhoto|length %}

{# display cover photo #}

{% endif %}


Loops like for enable iterating over arrays of assets, entries, etc:

{% for block in entry.body %}

{# display each block #}

{% endfor %}


Macros let you reuse display logic across templates:

{% macro displayImage(image) %}

{# display image #}

{% endmacro %}


Filters modify variables before output:

{{ entry.body|striptags|slice(0, 200) }}


Including other templates builds reusable components:

{% include '_components/hero' %}

This type of logic allows full control over how the templates and content from Craft CMS get rendered in the browser. It enables robust frontend experiences with dynamic data and personalization.

Twig provides the tooling for complete control over the presentation layer. Template designers can use its straightforward syntax for basic templating needs. And developers can leverage Twig to inject advanced Theme Logic and functionality as needed.

The combination of template variables and programming logic constructs like conditionals, loops, and macros is what enables Craft's Twig templates to move beyond basic static displays to fully dynamic, conversion-focused experiences.

Advanced Twig Features for Theme Logic

Template Inheritance

A powerful aspect of Twig templating is template inheritance. It allows child templates to inherit from parent templates utilizing the extends tag.

For example, all page templates in Craft CMS projects generally extend a base _layout template that contains the overall site scaffolding:

{% extends '_layouts/base' %}

Child templates inherit everything from the parent layout and can override blocks of content.

Template inheritance enables better architecture and reuse of Theme Logic. Shared layout code, macros, variables, etc. go in the parent template. Unique template-specific logic goes in the child.

Some benefits of template inheritance include:

  • Consistent layouts and UI elements

  • Centralized Theme Logic for easier management

  • Eliminates code duplication across templates

  • Promotes modular, reusable components

  • Streamlines template organization and structure

With template inheritance, developers can build robust parent templates with all the common Theme Logic like headers, footers, navs, etc. Then child templates simply fill in their unique content.

This model provides efficiency and maintains clean architecture as websites scale and grow. And it enables much easier management and updating of shared Theme Logic down the road.

Additional Twig Features

Some other advanced Twig features that enable more powerful Theme Logic are:

Blocks - Blocks allow child templates to override sections of the parent content. Common for headers, footers, sidebars, etc.

Includes - Including other templates builds reusable components without inheritance.

Scopes - Scopes like set and with create local variables without polluting the global scope.

Filters - Filters transform and modify values on-the-fly before outputting.

Namespaces - Namespaces organize templates into groups for cleaner coding.

Cache - Caching fragments of templates improves performance by avoiding duplicate rendering work.

These features give developers fine-grained control over templates and Theme Logic. They facilitate building robust, complex websites and web apps within Twig.

For example, blocks allow extensive customization of layouts. Includes encourage modular design. Scopes and namespaces improve architecture. Filters enable formatting data on-demand. And caching optimizes performance.

Together they provide powerful capabilities for implementing advanced Theme Logic beyond just the basics. Craft CMS and Twig integrate these features into an easy-to-use templating experience for developers.

Managing and Organizing Templates

To optimize performance and enforce clean architecture, it's important to properly organize and manage Twig templates. Some tips include:

  • Logical naming conventions and namespaces

  • Modular design with includes and inheritance

  • Avoiding unnecessary and duplicate includes

  • Code commenting for clarity

  • Utilizing template caching for performance

  • Leveraging context-based template loading

  • Following DRY (don't repeat yourself) principles

  • Centralizing common logic in parent templates

Loading hundreds of templates on every request will bog down performance. With proper organization, only the minimal required templates need to load.

For example, context-based template loading means Craft will only load the _layouts/blog.twig layout on blog pages. Redundant includes are avoided. And centralized logic is inherited.

Performance optimizations like caching further improve load times by not repeatedly rendering complex template code.

Following coding best practices ensures the templates remain easy to manage over time as new logic and features are added. A clean, modular architecture using template inheritance lays the foundation for scalability.

Proper Twig template organization streamlines Theme Logic, avoids redundancies, and improves site performance. It sets the stage for developers to build complex logic upon.

Using Template Hooks for Theme Logic

Template Hook Examples

Template hooks provide control points in Craft CMS templates for modifying output without editing the original files. Some common examples are the 'head', 'footer', and 'entry' hooks. The 'head' hook allows injecting code within the head tag, useful for adding custom assets or meta tags. The 'footer' hook makes it easy to insert code right before the closing body tag. And the 'entry' hook targets the output of entry templates specifically, enabling tweaks to markup, variables, and display logic on entries across the site. Beyond just these, template hooks exist for emails, PDF generation, and many other display outputs.

By wrapping code in simple {% hook %} tags, developers can tap into templates globally and integrate custom logic. This makes it easy for plugins and modules to provide new presentation capabilities and functionality enhancements without directly touching template files. Template hooks thereby serve as powerful control points for modifying output and Theme Logic from anywhere across the CMS.

Modifying Output

The possibilities are wide open for how template hooks can customize and alter output. Hooks allow adding or removing HTML elements like divs, classes, ids, and more. They make it simple to inject new assets directly into the page output such as custom CSS, JavaScript, or images.

Hooks can also modify or filter key template variables on the fly before display. Segmenting output with conditional logic in a hook enables selective presentation by type, url, user role, and more. For more advanced control, hooks can even override template blocks to completely customize defined sections of a template. Beyond just display changes, hooks additionally enable performant actions like data saving, reporting, and communication with external services.

With such a robust toolkit, developers can truly customize and tailor the presentation layer in any way imaginable. For example, selectively removing site navigation on blog post entries with an 'entry' hook. Or globally filtering out unwanted content across all pages by modifying the 'html' hook. Even performing special actions like integrating analytics on 'head' or firing conversion pixels on 'footer'. Template hooks allow fine-grained control over output without being invasive.

Hook Best Practices

To properly leverage the power of template hooks, there are some best practices to keep in mind:

  • Use {% block %} tags to encapsulate hook content and avoid output conflicts

  • Keep hooks small and focused on one specific output modification

  • Name hooks semantically like 'newsletter_body' for clarity

  • Thoroughly document hooks for other developers

  • Test rigorously to ensure hooks don't break expected output

  • Avoid hooking templates unnecessarily if custom templates would suffice

  • Make hooks reusable and encapsulated as standalone customizations

An example of properly using {% block %} tags is wrapping hook content like:

{% block myCustomHook %}

{% endblock %}

This prevents issues with multiple hooks overlapping.

Following these best practices ensures template hooks integrate smoothly with minimal conflicts. Thoughtful use of blocks, naming conventions, documentation, and testing prevents unexpected issues down the road. The customization power of hooks is unlocked fully when they are implemented properly.

In summary, template hooks enable global modifications to presentation logic without being invasive. When used correctly, they allow for highly customized site display and functionality in Craft CMS. Template hooks are an invaluable tool for theming and customizing output.

Global Configuration Options


The config/general.php file serves as a central place outside of templates to define global configuration and logic in Craft CMS. It provides a way to set up common needs like environment variables, namespace aliases, custom namespaces, and global helper variables/functions. For example, developers can use general.php to define app settings like dev vs production modes using environment variables. Alias shortcuts can be created for lengthy namespace paths to make referencing them in Twig cleaner and simpler. Additional custom namespaces can be autoloaded to better organize code into logical segments like utils, components, data sources, etc. This removes clutter from the main templates/ folder. Global helper variables and functions can also be defined in general.php to make them accessible across all templates.

Best practices for general.php include only adding essential globally-needed variables and logic, keeping the file focused and avoiding bloat, thoroughly documenting everything included, and leveraging environment variables for API keys and other sensitive credentials. The global scope should be used judiciously. But general.php gives a controlled way to enable it.

Additional Namespaces

As mentioned, custom namespaces help organize code into logical segments and reduce naming collisions. For example, defining:

'namespaces' => [

'data' => function() {

return require 'data-sources.php';



Allows cleanly accessing namespaced logic in Twig:

{% set blogPosts = data.blog.recentPosts %}

Some conventions to follow when adding namespaces are:

  • Logical organization (utils, components, etc)

  • Semantic naming

  • UpperCamelCase names

  • Thorough documentation

Additional namespaces defined in general.php provide more structure for managing Theme Logic across complex projects.

Global Variables

Global template variables can also be defined in general.php like:

'globals' => [

'global' => \path\to\class::class,


Which are then accessible in Twig templates via:

{{ global.someMethod() }}

While this makes variables globally available, it can lead to namespace pollution, unpredictable dependencies, memory issues, and other problems if overused.

In general, focused scopes and proper namespacing are preferred over excessive globals. But general.php allows defining essential global variables in a controlled centralized way when they are absolutely needed.

In summary, the config/general.php file enables centralized global configuration of namespaces, variables, and settings for Craft CMS installs. When leveraged properly, it can help organize and manage Theme Logic at a global level outside of templates.

Using Modules for Reusable Logic

Module Examples

Modules in Craft CMS allow developers to encapsulate reusable logic into standalone components outside of templates. Some common examples of module types are widgets, fields, volumes, controllers, services, and asset bundles. Widget modules provide reusable frontend display components, like a custom carousel slideshow. Field modules enable creating editable input types and custom fields for content modeling. Volume modules help manage file uploads and assets. Controller modules group request handling logic and routes. Services house business logic and data access capabilities. And asset bundles handle packaging JavaScript and CSS assets efficiently.

Modules give a home for logic that can be reused across templates without requiring duplicated code. They help organize logic into portable, shareable units following modular design principles. By registering modules in the main config file, the functionality becomes available globally to integrate across the project.

Registering and Using Modules

To leverage a custom module in a Craft CMS project, the module class first needs to be registered in the config/app.php file:

'modules' => [

'my-module' => \modules\MyModule::class


This exposes the MyModule class everywhere in the codebase. Then in templates, the module can be included using a simple hook tag:

{% hook 'my-module' %}

{% include "my-module" %}

{% endhook %}

Any logic and helpers can then be accessed directly:

{{ myModule.someHelper }}

{% set slides = myModule.getSlides %}

This registration and inclusion model makes modules globally available while keeping them decoupled from templates. Some key benefits this provides are logic reuse without copying code, enforced architecture, customizable components, and improved extensibility. Overall, modules encourage modular thinking in design.

Modules Best Practices

To keep modules maintainable and effective as projects scale, some best practices to follow are:

  • Semantic naming like SeoManagerModule

  • Thorough documentation blocks

  • Clean method and variable naming

  • Avoiding business logic in display modules

  • Preferring small, focused modules

  • Watching for duplicate logic

  • Making modules customizable

For example:


  • Notification module for managing frontend alerts */ class NotificationModule {

// ...


This keeps modules organized and improves developer experience utilizing them.

In summary, modules allow logic reuse while encouraging modular architecture in Craft CMS. When leveraged effectively, they enforce separation of concerns and organization for sustainable growth.

Adding Logic with Custom Plugins

Plugin Examples

Plugins provide extensive capabilities for adding custom logic and functionality to Craft CMS, enabling the platform to be extended and customized in unlimited ways. Some common examples of plugin use cases are: building custom post types like Events or Jobs, creating advanced custom fields with validation and formatting, developing custom form builders and handlers for things like contact forms, integrating complex workflows for approval processes, handling third-party API integrations, building reusable components, and more. Plugins essentially give full control and flexibility to customize Craft beyond its out-of-the-box features. Anything that can be done in PHP can be integrated into the CMS via a custom plugin.

Plugin Development

When developing a custom plugin, some best practices to follow are: keeping business logic encapsulated in services rather than controllers, maintaining thin controllers focused solely on request handling and routing, namespacing the plugin's templates, variables, and components to avoid conflicts, having a clear README file for documentation, following PSR-4 autoloading standards, building reusable components and modules within the plugin, avoiding duplicated logic by making use of existing plugins and modules, and properly leveraging dependency injection.

Structuring the plugin properly from the start, such as having organized controller, service, variable, and template folders, keeps the plugin maintainable as it grows. Encapsulating business logic in services separates concerns cleanly:

// Plugin Service

namespace myplugin\Services;

class MyPluginService {

// Business logic methods


While thin controllers handle routing:

// Plugin Controller

public function actionSaveForm() {

// Get POST

// Validate

// Save via service

// Return response


Following these best practices results in modular, decoupled plugin logic.

Plugin Benefits and Drawbacks

Some benefits of implementing logic in a custom plugin include: ultimate customization and flexibility limited only by PHP, keeping templates uncluttered, enabling advanced use cases not otherwise possible, reusing logic across projects, decoupled and encapsulated logic, avoiding editing core files, ability to expose APIs and endpoints, and more.

Potential drawbacks can include performance overhead if excessive logic is implemented in plugins, increased complexity, enabling bad practices if not decoupled properly, additional versioning and maintenance burden, and risk of duplicate logic.

In general, plugins make the most sense for complex or advanced custom logic, where templates and modules would not suffice. They mouldCraft CMS into a truly custom solution tailored for any project need. But care should be taken to use plugins judiciously, only when the use case calls for functionality beyond standard solutions.

In summary, plugins provide endless extensibility to expand Craft's capabilities. When developed with best practices in mind, they empower fully customized CMS implementations.

Using Services for Business Logic

Creating Services

Services in Craft provide a way to encapsulate complex business logic and data access in a decoupled manner separate from controllers and templates. They are simply PHP classes that contain reusable methods for handling logic operations. Some examples of good use cases for custom services are creating API clients, performing complex calculations or processing, CRUD data operations, sending emails, integrating external services, processing submitted form data, queueing jobs, building reusable utilities, and more. Essentially any logic related to data, third-party services, business rules, etc can be housed in a service. This keeps the controllers thin and templates uncluttered.

Services get defined in a namespace under a module or plugin:

namespace modules\MyModule;

class MyService extends Component {

// logic methods


This allows the service to be injected cleanly wherever needed.

Injecting Services

To leverage a custom service, it can be injected into controllers, plugins, or templates. In a controller, dependency injection makes the service available:

public function __construct(MyService $service) {

$this->service = $service;


In a plugin, the service is instantiated:

$service = MyService::create();

And in templates, the service can be fetched:

{% set service = craft.app.get('myModule\\MyService') %}

This separation and injection allows keeping business logic truly decoupled.

Service Best Practices

To keep services effective, some tips are: namespace them under the related module/plugin, make them focused on a single responsibility, keep controllers thin and services fat, document public methods, follow PSR-4 autoloading standards, avoid tight coupling, unit test logic, and build reusable utilities where possible.

For example:

// web/MyModule/Services/MyService.php

namespace modules\MyModule\Services;


* Handles XYZ operations


class MyService {

// Methods


This keeps services organized, documented, and testable.

In summary, services enable encapsulation of business logic in Craft while enforcing decoupling and clean architecture through dependency injection. They are key for complex logic integrations.

Debugging Theme Logic

When issues arise in custom Theme Logic, Craft CMS provides helpful tools for debugging and troubleshooting.

Debugging with Dev Tools

Some key built-in tools include:

Debug Toolbar - Displays on the frontend with runtime info, performance metrics, and stack traces.

Devel Module - Adds debugging aids like Twig code tagging and variable dumps.

Twig Debug - Debugging tags like {% debug %} and {{ dump() }} output variable details.

For example, enabling the Debug Toolbar reveals runtime queries, memory usage, and more.

The devel module allows tagging Twig code blocks to measure performance:

{% debug %}

{# tagged code block #}

{% enddebug %}

And dumping variables:

{{ dump(entry) }}

These provide visibility into behavior and data.

Log Files and Stack Traces

Server and application logs also help identify issues:

craft/storage/logs - Runtime errors and exceptions.

craft/storage/runtime/logs - Server PHP errors.

Stack traces - Full stack traces on errors detail where they originate.

For example, an exception log:

[y-m-d h:i:s.u] Craft\Exception thrown: "Undefined variable: myVariable" in /templates/_components/header.twig on line 5.

Stack trace:

#0 [...stack trace...]

This pinpoints the exact file, line, and variables involved.

Log files provide visibility into errors and exceptions occurring in Theme Logic execution.

Common Bugs and Fixes

Some frequent Theme Logic bugs and fixes:

Undefined variables - Declare variables before use, fix typos.

Invalid syntax - Check Twig syntax, quotations, spacing.

Loop issues - Double check loop logic and array formatting.

Missing dependencies - Import required modules/plugins providing needed code.

Edge cases - Add sanity checks for empty values, data types, etc.

Cache conflicts - Clear caches if stale code is served.

Thoroughly logging errors and exceptions reveals the bug source. Then specific fixes can be applied.

Proactively adding sanity checks in Theme Logic also prevents bugs. For example:

{% if entry is defined and entry.coverImage|length %}

{# display image #}

{% endif %}

This validates data before use and avoids undefined errors.

Carefully inspecting stack traces, logs, and warnings helps identify and resolve Theme Logic bugs. Following best practices proactively can prevent many issues.

Testing and Documenting Theme Logic

Testing Tools and Best Practices

Craft provides helpful built-in testing tools and frameworks to validate custom Theme Logic, including Twig linting for syntax checking, PHPUnit for unit testing classes and logic, Codeception for full-stack browser and API testing, and mocking frameworks to isolate class dependencies.

Some best practices for leveraging these tools are: writing unit tests for services, complex logic, and critical classes using PHPUnit to validate behavior; linting all Twig templates to catch issues early; utilizing Codeception for thorough front-end browser and integration testing; isolating external dependencies by mocking them during unit tests; running all test suites locally and in CI/CD pipelines for quality; and treating tests themselves as documentation by using semantic assert descriptions.

For example, linting Twig templates proactively via:

twig-lint templates/

And a sample PHPUnit unit test:

public function testSomeLogic() {

$service = new MyService();

$this->assertEquals(5, $service->someLogic(2, 3));


Robust testing improves quality and documents expected behavior and edge cases.

Documentation and Comments

Thorough documentation also helps understand complex logic months down the road. Best practices are: commenting classes and methods; using semantic naming conventions; maintaining changelog files; including README docs in custom code; documenting templates and hooks; and avoiding ambiguous abbreviations.

For example:


* Returns collection of recent blog entries.


public function getRecentEntries() {}


{# Display sidebar ads #}

{% include "_ads" %}

This documents behavior unambiguously for future developers.

Avoiding Duplicate Logic

To avoid bloated code and inconsistencies, duplicate logic should be consolidated by: creating reusable utility functions/classes; building shareable logic into modules; leveraging template inheritance and includes; centralizing common patterns into parent templates or plugins; and watching for copied code across files.

For example, abstracting reusable social share buttons into an include:

{% include "_share-buttons" %}

Rather than duplicating.

Duplicate logic leads to harder maintenance, inconsistencies, bloated code, and performance challenges. Proper testing, documentation, and duplication avoidance sustain quality Theme Logic over time and improve velocity.

Shape April 2022 HR 202
Andy Golpys
- Author

Andy has scaled multiple businesses and is a big believer in Craft CMS as a tool that benefits both Designer, Developer and Client. 

Show us some love
Email Us
We usually reply within 72 hours
Agency Directory
Submit your agency
Affiliate Partners
Let's chat