Reference

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

{{propName}}

Component structure

Value from the component's interface prop

{{cms.field}}

CMS template page

Field from the current CMS item

{{item.field}}

List node children

Field from the current list item

{{itemIndex}}

List node children

Zero-based index of the current item

{{itemFirst}}

List node children

true for the first item in the list

{{itemLast}}

List node children

true for the last item in the list

{{isEditorMode}}

Anywhere

true in the editor, false in production build


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:

  • posts becomes post

  • categories becomes category

  • stories becomes story

  • children becomes child

  • author stays author (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

{{itemIndex}} or {{postIndex}}

Zero-based index

{{itemFirst}} or {{postFirst}}

true for first item

{{itemLast}} or {{postLast}}

true for last item

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

children

node, link

"children": "{{title}}"

attributes

All with attributes

"src": "{{imageSrc}}"

href

link

"href": "{{link}}"

props

component

"title": "{{cms.title}}"

if

All

"if": "{{showBanner}}"

html

embed

"html": "{{cms.body}}"

items

list

"items": "{{post.authorIds}}"

filter.value

list

"value": "{{category._id}}"

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:

  1. Named list context -- {{post.title}} matches a list variable named post

  2. Legacy item context -- {{item.field}} matches the current list item

  3. CMS context -- {{cms.field}} matches the current CMS template item

  4. Component props -- {{propName}} matches an interface prop

  5. Built-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

Building the future of digital experiences, one website at a time

© 2026 Company. All rights reserved.