PHPackages                             wabisoft/craft-bonsai-twig - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. wabisoft/craft-bonsai-twig

ActiveCraft-plugin[Templating &amp; Views](/categories/templating)

wabisoft/craft-bonsai-twig
==========================

Internal use template helper

9.2.6(2mo ago)01.3k↓82.7%PHPPHP &gt;=8.2.0

Since Jun 13Pushed yesterday1 watchersCompare

[ Source](https://github.com/wabi-soft/craft-bonsai-twig)[ Packagist](https://packagist.org/packages/wabisoft/craft-bonsai-twig)[ RSS](/packages/wabisoft-craft-bonsai-twig/feed)WikiDiscussions main Synced 3d ago

READMEChangelogDependencies (16)Versions (64)Used By (0)

Bonsai Twig Plugin
==================

[](#bonsai-twig-plugin)

Welcome to the **Bonsai Twig Plugin** README! This plugin is designed as a **development-only tool** to streamline your Twig templating experience by providing hierarchical template loading for various element types in Craft CMS 5.

Features
--------

[](#features)

- **Hierarchical Template Loading**: Automatically resolve templates for entries, categories, items, matrix blocks, and assets with intelligent fallback mechanisms
- **PHP 8.2 &amp; Craft CMS 5 Optimized**: Built with modern PHP features including null-safe operators and union types
- **Simple Debug Tools**: Clean, focused debugging that shows template paths and resolution without performance overhead
- **Development-Focused**: Designed specifically for development workflow - no production features or optimizations
- **Enhanced btPath() Function**: Returns complete HTML output with styling, eliminating need for manual Twig wrapping
- **LLM Trace Comments**: Opt-in, dev-only HTML comments mapping rendered DOM back to the winning template, consumable by AI agents reading page source
- **Zero Production Overhead**: Debug features return empty strings in production mode

Function Reference
------------------

[](#function-reference)

Each loader has its own doc with the full parameter list, resolution hierarchy, and a pure-Twig replacement if you ever remove the plugin:

- [`entryTemplates()`](readme-entry.md)
- [`categoryTemplates()`](readme-category.md)
- [`itemTemplates()`](readme-item.md)
- [`matrixTemplates()`](readme-matrix.md)
- [`assetTemplates()`](readme-asset.md)
- [`productTemplates()`](readme-product.md)

Also: [MIGRATION.md](MIGRATION.md) (v8 → v9 upgrade) · [CHANGELOG.md](CHANGELOG.md)

Requirements
------------

[](#requirements)

- **PHP**: 8.2.0 or higher
- **Craft CMS**: 5.0.0 or higher

Getting Started
---------------

[](#getting-started)

### 1. Install

[](#1-install)

The plugin ships inside the project as a Composer path repository:

```
// composer.json
{
    "repositories": [
        { "type": "path", "url": "_dev/plugins/craft-bonsai-twig" }
    ]
}
```

```
ddev composer require wabisoft/craft-bonsai-twig
ddev craft plugin/install bonsai-twig
```

### 2. Add your first template tree

[](#2-add-your-first-template-tree)

Call a loader where you'd normally write an `{% include %}`:

```
{{ entryTemplates({ entry }) }}
```

Then create templates under `templates/_entry/`. Bonsai tries paths from most to least specific and renders the first one that exists:

```
templates/_entry/
├── blog/
│   ├── article/
│   │   └── my-post.twig    ← just this slug
│   ├── article.twig        ← this entry type
│   └── default.twig        ← anything in blog
└── default.twig            ← global fallback

```

Start with a single `_entry/{section}/default.twig` and add more specific templates only where a page needs to differ — no template calls change, resolution picks them up automatically.

The same pattern applies to the other trees: `_matrix/`, `_category/`, `_item/`, `_asset/`, `_product/` — each with its own loader function (see [Usage Guide](#usage-guide)).

### 3. See what's resolving

[](#3-see-whats-resolving)

Add `?beastmode` to any URL (or press **Cmd+B**) to see every path tried and which one won. For AI agents reading page source, enable [LLM trace comments](#llm-trace-comments-v93) instead.

Template Resolution Strategy (v8.0)
-----------------------------------

[](#template-resolution-strategy-v80)

By default, templates resolve **section-first** (`entry/{section}/{type}/...`). In v8.0, you can opt into **type-first** resolution (`entry/{type}/{section}/...`), aligning with Craft 5's standalone entry types.

### Setting the Strategy

[](#setting-the-strategy)

Three levels of configuration (highest to lowest precedence):

**1. Per-template (highest priority):**

```
{{ entryTemplates({ entry: entry, strategy: 'type' }) }}
{{ itemTemplates({ entry: item, strategy: 'type' }) }}
```

**2. Config file:**

```
// config/bonsai-twig.php
return [
    'strategy' => 'type', // default is 'section'
];
```

**3. Control Panel:**

Radio buttons in Settings &gt; Bonsai Twig &gt; Template Resolution Strategy.

If unset at all levels, defaults to `'section'` — identical behavior to v7.

### Path Resolution Comparison

[](#path-resolution-comparison)

For an entry with section `blog` and type `article`:

PrioritySection-first (default)Type-first1`_entry/blog/article/{slug}``_entry/article/blog/{slug}`2`_entry/blog/article/_entry``_entry/article/blog/_entry`3`_entry/blog/{slug}``_entry/article/{slug}`4`_entry/blog/article``_entry/article/blog`5`_entry/blog/default``_entry/article/default`6`_entry/blog``_entry/article`7`_entry/article``_entry/blog`8`_entry/default``_entry/default`Strategies can be mixed — set a global default, then override per template call or per section. Patterns in [readme-entry.md](readme-entry.md) and [readme-item.md](readme-item.md).

### Which Loaders Support Strategy?

[](#which-loaders-support-strategy)

LoaderStrategy support`entryTemplates()`Yes`itemTemplates()`Yes (item + ctx dimensions)`matrixTemplates()`No (already type-centric)`categoryTemplates()`No (legacy, no entry types)`assetTemplates()`No (volume/folder based)---

Usage Guide
-----------

[](#usage-guide)

Six loader functions, one pattern: pass the element, get the most specific template that exists. Each heading links to the full reference (all parameters, complete hierarchy).

### Entry — [`entryTemplates()`](readme-entry.md)

[](#entry--entrytemplates)

```
{{ entryTemplates({ entry }) }}
{{ entryTemplates({ entry, style: 'featured', strategy: 'type' }) }}
```

### Category — [`categoryTemplates()`](readme-category.md)

[](#category--categorytemplates)

```
{{ categoryTemplates({ entry: category }) }}
```

### Item — [`itemTemplates()`](readme-item.md)

[](#item--itemtemplates)

For related/nested entries; `style` participates in path resolution.

```
{% for item in entry.relatedItems.all() %}
    {{ itemTemplates({ entry: item, style: 'compact' }) }}
{% endfor %}
```

### Matrix — [`matrixTemplates()`](readme-matrix.md)

[](#matrix--matrixtemplates)

Style-, handle-, context-, and position-aware. Pass `loopIndex`/`loopLength` to expose a `loop` variable inside block templates.

```
{% for block in entry.matrixField.all() %}
    {{ matrixTemplates({
        block: block,
        ctx: entry,
        loopIndex: loop.index0,
        loopLength: loop.length,
    }) }}
{% endfor %}
```

### Asset — [`assetTemplates()`](readme-asset.md)

[](#asset--assettemplates)

Resolves by volume → folder → filename.

```
{{ assetTemplates({ asset: image }) }}
```

### Product — [`productTemplates()`](readme-product.md)

[](#product--producttemplates)

```
{{ productTemplates({ product }) }}
```

### Template Path Display — `btPath()`

[](#template-path-display--btpath)

Returns a self-styled HTML block listing every attempted path with the resolved one marked ✓. Call it anywhere in a template — no wrapping needed; returns an empty string in production.

```
{{ btPath() }}

{# Or inside an HTML comment for minimal visual impact #}

```

Debug Features
--------------

[](#debug-features)

The plugin provides debugging tools that are only active in development mode (`devMode = true`).

### Debug Mode

[](#debug-mode)

#### Keyboard Shortcut (Recommended)

[](#keyboard-shortcut-recommended)

Press **Cmd+B** (Mac) or **Ctrl+B** (Windows/Linux) to open the Beastmode options modal. This lets you select which template types to debug without manually editing the URL.

#### URL Parameter

[](#url-parameter)

Add `?beastmode` to any URL to enable debug mode for all template types:

```
https://yoursite.test/some-page?beastmode

```

Or filter by specific types (comma-separated):

```
https://yoursite.test/some-page?beastmode=entry,matrix

```

Valid types: `entry`, `category`, `item`, `matrix`, `asset`, `product`, or `all`

### Debug Information Display

[](#debug-information-display)

When debug mode is active, you'll see clean debug output showing:

#### Template Resolution Information

[](#template-resolution-information)

- **Template Paths**: All attempted template paths in priority order
- **Resolved Template**: The template that was successfully loaded (marked with ✓)
- **Template Type**: Context information (Entry, Matrix, Category, Item)

The debug output focuses on essential information without performance metrics or complex styling.

Every loader call gains the overlay automatically — no template changes needed.

### LLM Trace Comments (v9.3)

[](#llm-trace-comments-v93)

When enabled, every Bonsai-resolved render is bracketed in machine-parseable HTML comments mapping the rendered DOM back to the winning template and its resolution context — the same map the beastmode overlay shows, but inline in the page source, consumable by an LLM/agent:

```

…rendered template output…

```

Pairs match on `id` (a per-page nonce + counter, so page content can't forge plausible pairs); nested loader calls yield nested pairs, so the comment tree mirrors the render tree. A page-level `\` marker is emitted whenever tracing is active — even on pages with no Bonsai renders. Comments carry paths, ids, and handles only — never field values.

The full attribute grammar lives in [example.CLAUDE.md](example.CLAUDE.md) — the one file consuming agents read.

#### Enabling

[](#enabling)

Two independent conditions, both required:

```
emit == devMode === true AND llmMode === true

```

`llmMode` is the switch; `devMode` is the safety floor — trace comments **never** render in production, even with `BONSAI_LLM_MODE=true` in a production `.env`.

> **Staging caveat:** `tried` enumerates your template tree and content-model handles in every page's source. Don't enable `llmMode` on internet-reachable staging servers that run `devMode=true`.

Enable via any of (precedence: env &gt; config file &gt; CP setting):

```
// config/bonsai-twig.php
return [
    'llmMode' => true,
];
```

```
# .env
BONSAI_LLM_MODE=true
```

Or toggle "LLM trace comments" in the plugin's CP settings.

#### Opting out (non-HTML contexts)

[](#opting-out-non-html-contexts)

A wrapped template rendering into JSON-LD, ``, ``, ``, an attribute value, or whitespace-sensitive output (``) would be corrupted by an HTML comment:

```
{# Suppress wrapping for this render and everything it renders #}
{{ entryTemplates({ entry, bonsaiTrace: false }) }}
```

#### Labelling static components

[](#labelling-static-components)

Trace comments mark dynamic resolutions only — plain `{% include %}`s are followed by reading source. For components included through *dynamic* names (or just for convenience), templates can self-label using `bonsaiTraceEnabled()`, which gates on the same devMode + llmMode switch:

```
{# Inline, inside any component #}
{% if bonsaiTraceEnabled() %}{% endif %}
```

```
{# Or as a shared macro, e.g. templates/_macros/dev.twig. This form calls the
   plugin instance instead of the Twig function, so templates keep compiling
   if the plugin is ever removed (an undefined Twig function is a
   compile-time error). Note: _self inside a macro names the macro's own
   file, so the caller must pass it. #}
{% macro trace(tpl) -%}
    {%- set bonsai = craft.app.plugins.getPlugin('bonsai-twig') -%}
    {%- if bonsai and bonsai.traceEnabled() %}{% endif -%}
{%- endmacro %}

{# In a component: #}
{% import '_macros/dev' as dev %}
{{ dev.trace(_self) }}
```

#### Agent consumption recipe

[](#agent-consumption-recipe)

[example.CLAUDE.md](example.CLAUDE.md) is a ready-to-append snippet for a consumer project's CLAUDE.md — the comment grammar, how to fetch raw source, nesting semantics, and how to read a fallthrough:

```
cat vendor/wabisoft/craft-bonsai-twig/example.CLAUDE.md >> CLAUDE.md
```

Integration with Craft 5
------------------------

[](#integration-with-craft-5)

### Unified Element Model

[](#unified-element-model)

In Craft CMS 5, categories are now entries, which simplifies template handling. The plugin automatically handles this unification while maintaining backward compatibility.

### Integration with Craft 5 `render()`

[](#integration-with-craft-5-render)

The plugin works alongside Craft 5's built-in `render()` method. While `render()` looks for templates in `_partials/{elementType}/{elementName}.twig`, Bonsai Twig provides more sophisticated hierarchical resolution.

**Craft 5 render():**

```
{{ entry.render() }}  {# Looks for _partials/entry/blog.twig #}
```

**Bonsai Twig:**

```
{{ entryTemplates({ entry }) }}  {# Checks multiple hierarchical paths #}
```

You can use both approaches as needed - `render()` for simple cases and Bonsai Twig for complex hierarchical template systems.

Development-Only Focus
----------------------

[](#development-only-focus)

This plugin is designed specifically as a development tool and includes:

### Simplified Architecture

[](#simplified-architecture)

- **No Caching**: Templates change frequently in development, so no caching overhead
- **Direct File System Checks**: Simple template existence checking without optimization layers
- **Minimal Dependencies**: Only essential services for template loading
- **Straightforward Logic**: Easy to understand and maintain codebase

### Basic Security

[](#basic-security)

- **Path Sanitization**: Basic path cleaning to prevent directory traversal
- **Input Validation**: Simple parameter type checking
- **Safe Property Access**: Uses null-safe operators for element properties

Migration
---------

[](#migration)

Upgrading from v8? The v9 breaking changes (underscore path prefixes, plugin handle, config file rename) are covered in [MIGRATION.md](MIGRATION.md).

Troubleshooting
---------------

[](#troubleshooting)

### Debug Mode Not Working

[](#debug-mode-not-working)

1. Ensure `devMode = true` in your Craft configuration
2. Check that you're using the correct URL parameter: `?beastmode`
3. Verify the plugin is installed and enabled

### Templates Not Found

[](#templates-not-found)

1. Use debug mode to see which paths are being checked: `?beastmode`
2. Verify your template directory structure matches the expected hierarchy
3. Check file permissions on template directories

### Template Resolution Issues

[](#template-resolution-issues)

1. Use debug mode to see which paths are being checked: `?beastmode` or `?beastmode=entry,matrix`
2. Use the enhanced `btPath()` function in your templates to see resolution info
3. Consider simplifying complex template hierarchies

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance95

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity71

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~18 days

Recently: every ~0 days

Total

59

Last Release

60d ago

Major Versions

v4.x-dev → 6.0.22024-08-05

v5.x-dev → 6.0.32024-11-07

6.4.0 → 7.0.02025-09-10

v6.x-dev → 8.0.02026-03-12

v8.x-dev → 9.0.02026-04-09

PHP version history (2 changes)0.0.1PHP &gt;=8.0.2

6.0.2PHP &gt;=8.2.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/40344699?v=4)[Wabisoft](/maintainers/wabisoft)[@wabisoft](https://github.com/wabisoft)

---

Top Contributors

[![dustinwalker](https://avatars.githubusercontent.com/u/360177?v=4)](https://github.com/dustinwalker "dustinwalker (175 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/wabisoft-craft-bonsai-twig/health.svg)

```
[![Health](https://phpackages.com/badges/wabisoft-craft-bonsai-twig/health.svg)](https://phpackages.com/packages/wabisoft-craft-bonsai-twig)
```

###  Alternatives

[verbb/vizy

A flexible visual editor field for Craft.

4250.4k](/packages/verbb-vizy)[verbb/hyper

A user-friendly links field for Craft.

24147.8k12](/packages/verbb-hyper)[verbb/footnotes

Adds a footnotes feature to CKEditor fields and Twig templates.

213.6k](/packages/verbb-footnotes)[froala/craft-froala-wysiwyg

Craft 3 CMS plugin for Froala WYSIWYG HTML Rich Text Editor.

1719.1k](/packages/froala-craft-froala-wysiwyg)[vierbeuter/craft-footnotes

Adds a footnotes feature to CKEditor fields and Twig templates.

212.7k](/packages/vierbeuter-craft-footnotes)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
