somehow-digital / craft-stack
Brings a namespace system to better organize and load Twig templates in Craft CMS.
Package info
github.com/somehow-digital/craft-stack
Type:craft-plugin
pkg:composer/somehow-digital/craft-stack
Requires
- php: ^8.2
- craftcms/cms: ^5.8.0
Requires (Dev)
- roave/security-advisories: dev-latest
This package is auto-updated.
Last update: 2026-03-28 13:42:16 UTC
README
Stack for Craft CMS
Brings a namespace system to better organize and load Twig templates in Craft CMS.
Stack provides a namespace system to resolve template files and behaves similar to how Twig's native
FilesystemLoader works. Under the
hood it uses Craft's Template Roots mechanism.
Currently, this plugin works in frontend mode only. (It does not work with Craft's panel templates.)
Requirements
- Craft CMS 5.8.0 or later.
- PHP 8.2 or later.
Installation
Install this plugin from the Plugin Store or via Composer.
Plugin Store
Go to the “Plugin Store” in your project’s Control Panel, search for “stack” and click on the “Install” button in its modal window.
Composer
composer require somehow-digital/craft-stack ./craft plugin/install stack
Configuration
Settings can be configured via a config/stack.php config file.
prefix- handle prefix for namespaced template file paths.
default: @
namespaces- list of namespaces for template file path resolution.
default: []
- By default, no namespaces are configured, and the plugin will not resolve templates.
- Order of namespaces matters when template file paths are resolved via
Dynamic Resolution. handleandpathvalues can use object templates, whereSite(site) andSiteGroup(group) objects are available to use.handlevalues are optional but are needed ifStatic Resolutionis to be used.contextvalues are optional and are used to pass variables to be used in object templates.
config/stack.php
<?php return [ // 'prefix' => '@', 'namespaces' => [ [ 'handle' => '{site.handle|lower}', 'path' => 'sites/{site.handle|lower}', ], [ 'handle' => '{group.name|lower}', 'path' => 'groups/{group.name|lower}', ], [ 'handle' => 'global', 'path' => '_global/{env}', 'context' => [ 'env' => App::devMode() ? 'dev' : '', ], ], ], ];
Usage
Due to how Craft's template roots work, if a defined template path resolves to an existing template file, Stack's namespace system will not be used, because Craft will resolve existing template files, before configured template roots are evaluated. This also means that Stack will not interfere with template paths of existing template files.
Dynamic Resolution
The template file will be resolved by the order of the configured namespaces and their paths.
- If the current site's handle is
mysiteand the current site-group's name ismygroup, the first resolved template path istemplates/sites/mysite/header.twig. If this template file exists, it will be used. - If it doesn't exist, the next resolved template path is
templates/groups/mygroup/header.twig. If this template file exists, it will be used. - If it doesn't exist, the last resolved template path is
templates/_global/header.twig. If this template file exists, it will be used. - If it doesn't exist, Craft will throw the default
TemplateNotFoundexception.
{# resolves to `templates/sites/craft/header.twig` or `templates/groups/craft/header.twig` or `templates/_global/header.twig` #} {% include 'header.twig' %}
Static Resolution
Prefixing a template file path with a namespace handle, a template file for a specific namespace can be used.
If the template does not exist, Craft will throw a TemplateNotFound exception.
The namespace prefix will be evaluated via the handle value defined in the config file.
- If the current site's handle is
mysite, the resolved template path istemplates/sites/mysite/header.twig. If this template file exists, it will be used. - If it doesn't exist, Craft will throw the default
TemplateNotFoundexception.
{# resolves to `templates/groups/mygroup/header.twig` #} {% include '@mygroup/header.twig' %}
{# resolves to `templates/sites/mysite/header.twig` #} {% include '@mysite/header.twig' %}
{# resolves to `templates/_global/header.twig` #} {% include '@global/header.twig' %}