Template Expressions Reference
Meno uses {{expression}} mustache-style syntax to insert dynamic values into node properties. The available expressions depend on the rendering context: component structure, CMS template page, or list node children.
Expression Contexts
Expression | Context | Description |
|---|---|---|
| Component structure | Value from the component's interface prop |
| CMS template page | Field from the current CMS item |
| List node children | Field from the current list item |
| List node children | Zero-based index of the current item |
| List node children |
|
| List node children |
|
| Anywhere |
|
Component Props
Inside a component's structure, use {{propName}} to reference interface props.
{
"component": {
"interface": {
"title": { "type": "string", "default": "Hello" },
"imageSrc": { "type": "file", "accept": "image/*", "default": "" },
"link": { "type": "link", "default": { "href": "/" } }
},
"structure": {
"type": "node",
"tag": "div",
"children": [
{
"type": "node",
"tag": "img",
"attributes": { "src": "{{imageSrc}}", "alt": "{{title}}" }
},
{ "type": "node", "tag": "h2", "children": "{{title}}" },
{
"type": "link",
"href": "{{link}}",
"children": ["View details"]
}
]
}
}
}The expression {{propName}} is replaced with the prop's current value at render time. If the prop has no value set on the instance, the default from the interface is used.
CMS Template Pages
Template pages render one HTML file per CMS item. Use {{cms.field}} to access fields from the current item.
{
"type": "node",
"tag": "article",
"children": [
{ "type": "node", "tag": "h1", "children": "{{cms.title}}" },
{
"type": "node",
"tag": "time",
"attributes": { "datetime": "{{cms.publishedAt}}" },
"children": "{{cms.publishedAt}}"
},
{
"type": "node",
"tag": "img",
"attributes": { "src": "{{cms.coverImage}}", "alt": "{{cms.title}}" }
},
{ "type": "embed", "html": "{{cms.body}}" }
]
}Reference Field Access
Access fields from referenced items using dot notation:
{
"type": "node",
"tag": "div",
"children": [
{ "type": "node", "tag": "span", "children": "By {{cms.author.name}}" },
{
"type": "node",
"tag": "img",
"attributes": { "src": "{{cms.author.avatar}}", "alt": "{{cms.author.name}}" }
}
]
}The reference is resolved automatically. If the author field is a reference type pointing to the authors collection, {{cms.author.name}} retrieves the name field from the referenced author item.
System Fields
System fields are also available:
{
"type": "node",
"tag": "div",
"children": [
{ "type": "node", "tag": "span", "children": "ID: {{cms._id}}" },
{ "type": "node", "tag": "span", "children": "Created: {{cms._createdAt}}" },
{ "type": "node", "tag": "span", "children": "Updated: {{cms._updatedAt}}" }
]
}List Item Context
Inside a list node's children, item expressions reference the current iteration item.
Default Variable Name
For sourceType: "prop", the default variable is item:
{
"type": "list",
"sourceType": "prop",
"source": "features",
"children": [
{ "type": "node", "tag": "h3", "children": "{{item.title}}" },
{ "type": "node", "tag": "p", "children": "{{item.description}}" }
]
}For sourceType: "collection", the default variable is the singularized collection name:
{
"type": "list",
"sourceType": "collection",
"source": "posts",
"children": [
{ "type": "node", "tag": "h3", "children": "{{post.title}}" }
]
}Singularization rules:
postsbecomespostcategoriesbecomescategorystoriesbecomesstorychildrenbecomeschildauthorstaysauthor(already singular)
Custom Variable Name (itemAs)
Override the default variable name with itemAs:
{
"type": "list",
"sourceType": "collection",
"source": "posts",
"itemAs": "article",
"children": [
{ "type": "node", "tag": "h2", "children": "{{article.title}}" },
{ "type": "node", "tag": "p", "children": "{{article.excerpt}}" }
]
}Index and Position Variables
Each item context provides index and position variables. These use the item variable name as a prefix:
Variable | Description |
|---|---|
| Zero-based index |
|
|
|
|
The legacy item-prefixed variants ({{itemIndex}}, {{itemFirst}}, {{itemLast}}) are always available for backward compatibility, even when using a custom itemAs name.
{
"type": "list",
"sourceType": "collection",
"source": "posts",
"children": [
{
"type": "node",
"tag": "div",
"attributes": { "data-index": "{{postIndex}}" },
"children": [
{ "type": "node", "tag": "span", "children": "{{post.title}}" },
{
"type": "node",
"tag": "span",
"if": "{{postFirst}}",
"children": "NEW"
},
{
"type": "node",
"tag": "hr",
"if": "{{postLast}}",
"style": { "base": { "display": "none" } }
}
]
}
]
}Reference Fields in Lists
Dot notation works for referenced items inside lists:
{
"type": "list",
"sourceType": "collection",
"source": "posts",
"children": [
{ "type": "node", "tag": "h3", "children": "{{post.title}}" },
{ "type": "node", "tag": "span", "children": "By {{post.author.name}}" },
{
"type": "node",
"tag": "img",
"attributes": { "src": "{{post.category.icon}}", "alt": "{{post.category.label}}" }
}
]
}Nested Lists
Nested lists preserve parent contexts. Inner list children can access both their own item and the parent item:
{
"type": "list",
"sourceType": "collection",
"source": "categories",
"children": [
{ "type": "node", "tag": "h2", "children": "{{category.name}}" },
{
"type": "list",
"sourceType": "collection",
"source": "posts",
"filter": { "field": "categoryId", "operator": "eq", "value": "{{category._id}}" },
"children": [
{ "type": "node", "tag": "h3", "children": "{{post.title}}" },
{ "type": "node", "tag": "span", "children": "In: {{category.name}}" }
]
}
]
}Where Expressions Can Be Used
Template expressions are resolved in these node properties:
Property | Node Types | Example |
|---|---|---|
|
|
|
| All with attributes |
|
|
|
|
|
|
|
| All |
|
|
|
|
|
|
|
|
|
|
In Attributes
{
"type": "node",
"tag": "div",
"attributes": {
"id": "section-{{item._id}}",
"data-category": "{{item.category}}",
"aria-label": "{{item.title}}"
}
}In Component Props
When placing a component inside a list or CMS template, pass dynamic values through props:
{
"type": "component",
"component": "PostCard",
"props": {
"title": "{{post.title}}",
"image": "{{post.coverImage}}",
"link": "{{post._url}}"
}
}In Conditional Rendering
{
"type": "node",
"tag": "span",
"if": "{{item.featured}}",
"style": { "base": { "color": "var(--warning)" } },
"children": "Featured"
}Editor Mode Detection
Use {{isEditorMode}} to conditionally show or hide elements based on whether the page is being viewed in the editor or in a production build.
{
"type": "node",
"tag": "div",
"if": "{{isEditorMode}}",
"style": { "base": { "padding": "8px", "background": "var(--info-bg)", "fontSize": "12px" } },
"children": "This message is only visible in the editor."
}Internationalized Values
Prop values and CMS fields can use the _i18n object format for per-locale content. The template engine automatically resolves the correct value based on the current locale.
In component instances:
{
"type": "component",
"component": "Hero",
"props": {
"title": { "_i18n": true, "en": "Welcome", "pl": "Witaj", "de": "Willkommen" },
"subtitle": { "_i18n": true, "en": "Get started today", "pl": "Zacznij dzisiaj" }
}
}In node children (for static text that varies by locale):
{
"type": "node",
"tag": "h1",
"children": { "_i18n": true, "en": "Hello", "pl": "Hej" }
}The _i18n marker ("_i18n": true) identifies the object as a localized value. Other keys are locale codes matching those defined in project.config.json.
Expression Resolution Order
When a template expression like {{name}} is encountered, the renderer checks these contexts in order:
Named list context --
{{post.title}}matches a list variable namedpostLegacy item context --
{{item.field}}matches the current list itemCMS context --
{{cms.field}}matches the current CMS template itemComponent props --
{{propName}}matches an interface propBuilt-in variables --
{{isEditorMode}},{{itemIndex}}, etc.
If no context matches, the expression is left as-is in the output (which can be useful for client-side template processing by MenoFilter).
Next Steps
Node Types -- node properties that accept expressions
Component Props -- defining interface props
CMS Fields -- field types available in
{{cms.field}}MenoFilter API -- client-side template rendering