Intro
PageBlocks registers seven MODX snippets on install. They exist so the component is reachable from an ordinary MODX template: you paste a call, you get output. No controller, no route, no migration to our template engine.
| Snippet | What it does |
|---|---|
pbList | Universal lister with built-in pagination over any model |
pbBlocks | Renders the constructor blocks of a resource |
pbResources | Flat list of MODX resources |
pbUsers | Flat list of users |
pbMenu | Resource tree for navigation |
pbFilters | Ready-made filter form for a constructor table |
pbJson | Renders a JSON value through an inline template |
Calling them
Both spellings work. In a MODX template:
[[!pbList? &model=`PbResource` &parent=`[[*id]]` &tpl=`myItem` &limit=`10`]]In a Fenom template:
{'!pbList' | snippet: ['model' => 'PbResource', 'tpl' => 'myItem', 'limit' => 10]}Keep the ! — these snippets are meant to be uncached, otherwise MODX will freeze the first render into the page cache.
Array parameters are JSON
A MODX snippet receives every parameter as a string, so anything array-shaped is written as JSON and decoded by the snippet:
[[!pbList?
&model=`PbResource`
&tpl=`myItem`
&where=`{"template":4}`
&orderBy=`{"publishedon":"desc"}`
]]Invalid JSON is passed through untouched, so a plain 12,7,3 id list stays an id list.
Shared output parameters
pbResources, pbUsers, pbMenu and pbBlocks share one output contract — the same one pdoTools users already know:
| Parameter | Default | Description |
|---|---|---|
tpl | a default chunk of the component | Chunk rendering a single row. Without it the snippet falls back to its default chunk, so a bare [[!pbResources]] already prints something. |
tplWrapper | Chunk wrapping the whole output. Receives output and total. | |
tplEmpty | Chunk rendered instead of the output when nothing was found. | |
outputSeparator | \n | Glue between rendered rows. |
toPlaceholder | Put the output into a MODX placeholder and return nothing, so the call can sit at the top of a template and print further down. | |
showLog | Print what the snippet complained about next to its output. Shown to an authenticated manager only. |
Inside tpl the row is available as item, and its 1-based position as idx:
<div class="card">
<span class="num">{$idx}</span>
<a href="{$item->uri}">{$item->pagetitle}</a>
</div>Which chunk does tpl mean?
One rule for every snippet, and the same one the rest of PageBlocks uses — the source is part of the value, not a separate parameter:
&tpl=`myItem` → MODX chunk named myItem
&tpl=`file:chunks/myItem` → core/App/elements/chunks/myItem.tpl
&tpl=`file:pbShop:chunks/card` → file from a named source
&tpl=`@INLINE <b>{$item->pagetitle}</b>` → template spelled inline.tpl is appended when you leave it off. There is no fallback between the two: a file: that does not exist is an error in the log, not a quiet look-up in the database — so a typo never silently resolves to something else.
chunk: is deprecated
chunk: used to mean "the database" in pbList, while a bare name there meant a file — the opposite of pbResources, pbUsers and pbMenu, where a bare name has always been the database and chunk: rendered nothing at all. A bare name is now the database everywhere. chunk: still works for one major version and writes a deprecation line into the MODX log.
Zero step
Each of the list snippets ships a default chunk, installed as an ordinary chunk you can find in the element tree, edit in place, or copy next to it:
| Snippet | Default chunk |
|---|---|
pbList, pbResources | pageblocks.resource |
pbUsers | pageblocks.user |
pbMenu | pageblocks.menu + pageblocks.menu.wrapper |
pbBlocks | pageblocks.blocks.wrapper — the .pb-page wrapper the design tokens are declared on; it has no default row chunk, each block renders through the chunk the constructor gave it |
So [[!pbResources]] renders a list before you have written a single chunk. Pass &tpl and yours wins.
What an upgrade does to a default you have edited
The installer stores the sha1 of the shipped content of every default chunk, so an upgrade can tell your work from ours:
| State of the record | What the upgrade does |
|---|---|
| untouched (hash matches what we shipped) | overwritten, so fixes to the default actually reach you |
| edited by you | left alone, with a line in the MODX log naming the chunk and where the shipped original is |
| marked static | never touched, whatever the hash — the content of a static element is a file, and it is yours |
Upgrading from a version that did not store hashes yet? Then an edit is indistinguishable from a changed default, and the safe side wins: your version is kept.
static points at core/App, not at the component
Tick "static" on a default chunk and its content moves to core/App/elements/chunks/<name>.tpl — the site-owned tree, which an upgrade only ever adds to. Pointing it inside core/components/pageblocks/ would mean the file resolver overwrites your content on every upgrade while formally "not touching the record".
Need the rows as data rather than HTML? That is what the helpers are for — resources(), users() — and they are the Fenom-side entry point anyway.
Where a snippet's code lives
The seven snippets are installed as static elements: the code that runs is the file under core/components/pageblocks/elements/snippets/, and the database record only registers the element. An upgrade replaces the file; the record is left alone.
Editing a snippet in the manager writes into the component
Because the element is static, saving it in the manager writes your changes into the component's own file — and the next upgrade overwrites that file. A snippet is component code: it is not meant to be edited, it is meant to be updated. Need different behavior? Copy the file next to it under your own name and register your own snippet — chunks are the part meant for your edits.
A file change needs a cache refresh to take effect: MODX compiles elements into core/cache/includes, and until the cache is cleared the compiled copy is what runs. So after a deploy that touched these files, clear the MODX cache — from the manager, or with $modx->getCacheManager()->refresh() from your own deploy script.
Why is it empty? &showLog
A snippet that renders nothing is the hardest thing to debug in MODX: the tag disappears from the page and the reason stays in the MODX log, where nobody looks while editing a template. So add &showLog=`1` and the snippet prints what it collected:
[[!pbList? &tpl=`crad` &showLog=`1`]]<pre class="pb-log">[Fenom] chunk 'crad' not found in the database; for a file write file:crad</pre>Other things it will tell you, instead of rendering nothing at all:
| Message | What happened |
|---|---|
chunk 'x' not found in the database; for a file write file:x | a typo in tpl, or the chunk lives in a file |
file chunks/x.tpl not found under … | file: points at a path that does not exist |
unknown model 'X' | &model does not resolve under PageBlocks\App\Models\ |
&table is required | pbFilters without a table |
table 'X' has no fields marked filterable | the table is there, the filter flags are not |
nothing found, the query returned no rows | the chunk is fine — the where/scopes matched nothing |
The block is shown only to a user logged into the manager, so &showLog can be left in a template while a page is being built: a visitor sees the output alone. Every message except "nothing found" also goes to the MODX error log, with the file and line it came from.