Skip to content

Template types ​

The template name starts with a prefix, and the prefix decides where it is read from.

PrefixSourceExample
@INLINEtemplate code in the string itselfview('@INLINE Hello, {$name}')
@FILE / file:a file under pageblocks_elements_pathview('file:blocks/card.tpl')
file:<source>:a file from a named sourceview('file:pbShop:chunks/card')
@PATH / path:a file at an arbitrary pathview('path:assets/chunks/hero')
no prefixa MODX chunk (modChunk) by nameview('header')

There is no fallback

The prefix picks one source, it does not define a search order. view('header') looks for a chunk named header and does not then try files; view('file:header') looks for a file and never looks at chunks. When nothing matches, the reason goes to the MODX log naming the source it looked in — [Fenom] chunk 'header' not found in the database; for a file write file:header — and the call returns an empty string. A blank spot on the page instead of an error is almost always this.

template: is not one of these prefixes

A MODX template from the database is reachable from inside a template — {extends 'template:base'} — because template is a registered Fenom provider. But view() and &tpl do not parse that prefix: the whole string is taken as a chunk name, so view('template:base') logs chunk 'template:base' not found in the database and returns nothing. Keep a layout in a file or a chunk.

Named sources ​

A component or a project registers a directory once under a name, and templates are addressed through it — file:<source>:<path>:

php
view('file:pbShop:chunks/card');          // from the pbShop source
{include 'pbShop:chunks/card'}            // the same source inside a template
[[!pbList? &tpl=`file:pbShop:chunks/card`]]

The point is that the path lives in one place: the directory moves and the registration changes, not every template that referenced it. There is deliberately no separate "path" parameter — the source is named inside the value, otherwise &tpl, &tplWrapper and &tplEmpty in one call could not come from different places.

Three ways to register, all equal:

WhoHow
PageBlocks itselfthe pageblocks source — the default chunks it ships
your projectcore/App/elements.php, returning ['landing' => MODX_BASE_PATH . 'assets/themes/landing/tpl']
a satellite component (pbShop, pbAuth)ElementSources::register('pbShop', $dir) from its own event handler — the same way ApiRegistry is fed
php
use Boshnik\PageBlocks\View\ElementSources;

ElementSources::register('pbShop', MODX_CORE_PATH . 'components/pbshop/elements/chunks');

A satellite has an event of its own — OnPageBlocksSourcesRegister. It fires when the registry is first assembled, after core/App/elements.php, and exists so that a satellite does not have to guess which MODX event to hook into:

php
// A satellite plugin subscribed to OnPageBlocksSourcesRegister
ElementSources::register('pbShop', MODX_CORE_PATH . 'components/pbshop/elements/chunks');

Registering late is fine: a satellite boots after the view does, so the Fenom provider for a source is created on first use.

An unknown source is an error, not a path

file:pbshp:card — a typo in the name — does not fall back to looking for a file called pbshp:card. It logs unknown element source 'pbshp' and lists the sources that are registered. file, path, template, chunk and inline cannot be used as names: they already mean something in the syntax.

Extension ​

File templates (file:, path:) may omit the extension — .tpl is appended. An existing .tpl or .html is taken as is; any other extension is not recognized as one and .tpl gets appended on top of it.

php
view('file:blocks/article');       // → blocks/article.tpl
view('file:blocks/article.tpl');   // → blocks/article.tpl
view('file:mail/invoice.html');    // → mail/invoice.html

📁 Where file templates live ​

file: and @FILE are resolved against pageblocks_elements_path, core/App/elements/ by default:

php
view('file:blocks/article.tpl');
// => looks for: core/App/elements/blocks/article.tpl

path: and @PATH carry the directory themselves and ignore pageblocks_elements_path. The path is site-relative or absolute, understands the {core_path}, {base_path} and {assets_path} placeholders, and may not contain ..:

php
view('path:assets/chunks/hero');            // → /assets/chunks/hero.tpl
view('path:{core_path}elements/mail/ok');   // → core/elements/mail/ok.tpl

This is the same mechanism as the block's Chunk directory field (chunk_path): it lets chunks sit next to the code that uses them instead of piling up in one directory.

Inside a template ​

The same prefixes work in {include}, {extends} and {insert} — they are engine providers, not view() parsing:

fenom
{extends 'file:templates/base.tpl'}
{include 'path:assets/chunks/hero'}
{include 'header'}            {* no prefix - a MODX chunk *}

Files only

With pageblocks_file_elements_only = true the template: provider is not registered at all and database chunks stop being reachable. Note the asymmetry: inside a template an unprefixed {include 'header'} falls back to the file provider, while view() and &tpl refuse an unprefixed name outright and say so in the log — they are the surface a snippet call is copy-pasted into, and a name that means a chunk on one site and a file on another cannot be debugged.

© PageBlocks 2019-present