Template types
The template name starts with a prefix, and the prefix decides where it is read from.
| Prefix | Source | Example |
|---|---|---|
@INLINE | template code in the string itself | view('@INLINE Hello, {$name}') |
@FILE / file: | a file under pageblocks_elements_path | view('file:blocks/card.tpl') |
file:<source>: | a file from a named source | view('file:pbShop:chunks/card') |
@PATH / path: | a file at an arbitrary path | view('path:assets/chunks/hero') |
| no prefix | a MODX chunk (modChunk) by name | view('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>:
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:
| Who | How |
|---|---|
| PageBlocks itself | the pageblocks source — the default chunks it ships |
| your project | core/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 |
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:
// 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.
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:
view('file:blocks/article.tpl');
// => looks for: core/App/elements/blocks/article.tplpath: 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 ..:
view('path:assets/chunks/hero'); // → /assets/chunks/hero.tpl
view('path:{core_path}elements/mail/ok'); // → core/elements/mail/ok.tplThis 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:
{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.