somehow-digital/craft-stack

Brings a namespace system to better organize and load Twig templates in Craft CMS.

Maintainers

Package info

github.com/somehow-digital/craft-stack

Homepage

Documentation

Type:craft-plugin

pkg:composer/somehow-digital/craft-stack

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.1.0 2026-03-28 13:31 UTC

This package is auto-updated.

Last update: 2026-03-28 13:42:16 UTC


README

Stack Logo

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.
  • handle and path values can use object templates, where Site (site) and SiteGroup (group) objects are available to use.
  • handle values are optional but are needed if Static Resolution is to be used.
  • context values 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.

  1. If the current site's handle is mysite and the current site-group's name is mygroup, the first resolved template path is templates/sites/mysite/header.twig. If this template file exists, it will be used.
  2. 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.
  3. If it doesn't exist, the last resolved template path is templates/_global/header.twig. If this template file exists, it will be used.
  4. If it doesn't exist, Craft will throw the default TemplateNotFound exception.
{# 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.

  1. If the current site's handle is mysite, the resolved template path is templates/sites/mysite/header.twig. If this template file exists, it will be used.
  2. If it doesn't exist, Craft will throw the default TemplateNotFound exception.
{# 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' %}

somehow digital.