Nested tags, caching, and mosquito conditionals
Last updated Sep 25th, 2026 | Page history | Improve this page | Report an issue
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$204 per month—let's make that $500!
Learn moreMODX parses nested tags inside out and treats cached and uncached tags differently. That is useful when you nest a TV inside a Snippet property. It also explains why a conditional with full tags in both branches can run expensive code you never meant to run.
This page expands the short notes on Tag Syntax. The caching rules come from Mark Hamstra's guidelines and nested caching write-up. The "mosquito" pattern is from Jason Coward's post.
Parsing order (cached first)¶
On each pass the parser finds tags and runs them by priority, then checks the result for more tags and repeats.
Typical priority:
- Nested cached tag
- Cached tag
- Nested uncached tag
- Uncached tag
Source order also matters: a tag earlier in the template runs before a later one at the same priority.
Uncached tags wait until cached work for that pass is done. When the Resource is cacheable, the page cache stores the HTML with uncached tags still present. Later requests only re-run those ! tags.
Nested example¶
Start with a TV filter that embeds chunks:
[[*featured:is=`1`:then=`[[$chunkA]]`:else=`[[$chunkB]]`]]
The parser resolves the inner [[$chunkA]] and [[$chunkB]] (and anything inside them) before it evaluates :is. After that pass you may see something like:
[[*featured:is=`1`:then=`Title-[[!snippetA]]`:else=`Title-[[!snippetB]]`]]
Then the cached TV/filter runs. Only one branch remains in the output, but both branches already ran. Any uncached Snippet left in the winning branch stays in the cache file for every request.
Caching guidelines (short)¶
| Situation | Prefer |
|---|---|
| Menus, listings, static chunks, Resource fields | Cached (no !) |
| Login / user profile / member-only output | Uncached |
| Output driven by query string or POST | Uncached |
| Snippet that only redirects | Uncached |
| External data with its own lifetime | Custom cache inside the Snippet |
Do not uncache Wayfinder or getResources just so edits "show up". Saving content clears the cache.
If an uncached Snippet sets placeholders, call those placeholders uncached too:
[[!Profile]]
Hello [[!+username]]
Why conditionals bite ("mosquitoes")¶
This looks neat and is often wrong for performance:
[[*id:is=`1`:then=`[[!FirstChildRedirect]]`:else=`[[$videoGallery]]`]]
Because parsing is inside out, FirstChildRedirect and $videoGallery both run before :is decides. You can get redirected even when the condition would have chosen the gallery.
Conditionals with full tags in :then / :else behave like mosquitoes: every nested tag takes a bite whether you needed that branch or not.
Build the tag from the condition result¶
Return tag tokens and names, not finished tags. Wrap the whole expression in an outer [[ ]] so the next parser pass forms a real tag:
[[[[*id:is=`1`:then=`!FirstChildRedirect`:else=`$videoGallery`]]]]
Order of work:
- Outer brackets wait.
- The inner
[[*id:is=...]]runs and returns either!FirstChildRedirector$videoGallery. - With the outer brackets that becomes
[[!FirstChildRedirect]]or[[$videoGallery]]. - Only that tag runs.
Snippet with no token: put ! in the returned name when you need it uncached (!FirstChildRedirect). Chunks keep $. To do nothing on the other branch, return a comment token such as - so the outer tag becomes [[-]] and is skipped.
Sidebox pattern¶
Jason's example chunk sidebox:
<div class="sidebox">
<h1>[[+title]]</h1>
[[[[+element]][[+properties]]]]
</div>
Call (names and properties assemble the next tag):
[[$sidebox?
&title=`[[getValue? &class=`modResource` &field=`menutitle` &where=`{"id":[[UltimateParent]]}`]]`
&element=`Wayfinder`
&properties=`@SidebarMenu? &startId=`[[UltimateParent]]` &level=`1``
]]
Placeholders only build the element name and property string. The heavy Snippet runs once, on the next pass.
Practical checks¶
- Search templates for
:then=[[and:else=[[. Prefer mosquito-style returns when those branches call Snippets or large Chunks. - Keep navigation and getResources cached unless the markup is truly per-user or per-request.
- After changing nesting, view the page HTML or use a parser/debug Extra and confirm unused branches do not appear as executed Snippets in the log.
See also¶
- Tag Syntax
- Output Filters / Modifiers
- Caching Guidelines for MODX Revolution (Mark Hamstra)
- Nested caching in MODX Revolution (Mark Hamstra)
- Tags as the Result or How Conditionals are like Mosquitoes (Jason Coward)
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$204 per month—let's make that $500!
Learn more










