Skip to main content

docs/guides/variables.md

# Variables Guide

Master variable access and assignment in templates.

## What are Variables?

Variables store data that you access in templates. They come from two sources:

1. **Context** - Data passed to the template
2. **Template Assignment** - Variables created in the template itself

## Accessing Variables from Context

The context is a map of data available to your template:

```elixir
context = %{
  "user" => %{"name" => "Alice"},
  "items" => [1, 2, 3]
}

Mau.render("{{ user.name }}", context)
# Output: "Alice"
```

### Simple Variable Access

```
{{ variable_name }}
```

Example:

```
context = %{"greeting" => "Hello"}
template = "{{ greeting }}"
# Output: "Hello"
```

### Property Access

Access properties with dot notation:

```
{{ user.name }}
{{ user.profile.email }}
{{ user.profile.contact.phone }}
```

Works with any nesting depth:

```
context = %{
  "user" => %{
    "name" => "Alice",
    "profile" => %{
      "contact" => %{
        "phone" => "555-1234"
      }
    }
  }
}

{{ user.profile.contact.phone }}
# Output: "555-1234"
```

### Array Access

Access array elements by index (0-based) using bracket notation:

```
{{ items[0] }}    # First item
{{ items[1] }}    # Second item
{{ items[5] }}    # Sixth item
```

Example:

```
context = %{
  "colors" => ["red", "green", "blue"]
}

{{ colors[0] }}   # "red"
{{ colors[2] }}   # "blue"
```

### Mixed Nesting

Combine object and array access:

```
{{ categories[0].name }}
{{ users[2].profile.email }}
```

Example:

```
context = %{
  "categories" => [
    %{"name" => "Books"},
    %{"name" => "Videos"}
  ]
}

{{ categories[0].name }}    # "Books"
{{ categories[1].name }}    # "Videos"
```

## Undefined Variables

Accessing undefined variables returns empty string:

```
context = %{"greeting" => "Hello"}

{{ undefined_var }}    # Renders as "" (empty)
{{ greeting }}         # Renders as "Hello"
```

Safe navigation example:

```
{% if user.email %}
  Email: {{ user.email }}
{% else %}
  Email not provided
{% endif %}
```

## Creating Variables with Assign

Create or modify variables within templates using `{% assign %}`:

### Basic Assignment

```
{% assign greeting = "Hello" %}
{{ greeting }}    # Output: "Hello"
```

### Assigning Literal Values

```
{% assign name = "Alice" %}
{% assign age = 30 %}
{% assign active = true %}
{% assign items = [] %}
```

### Assigning from Other Variables

```
{% assign first_item = items[0] %}
{{ first_item }}
```

### Assigning from Expressions

```
{% assign total = price | plus(tax) %}
Total: {{ total }}
```

### Assigning from Filters

```
{% assign first_name = user.name | split(" ") | first %}
{{ first_name }}
```

## Variable Scope

Variables are available from the point of assignment onward:

```
Start: {{ greeting }}        {# Empty, not assigned yet #}

{% assign greeting = "Hello" %}

After: {{ greeting }}        {# "Hello", now assigned #}
```

### Loop Scope

Loop variables are available inside the loop and accessible via `forloop`:

```
{% for item in items %}
  Item: {{ item }}           {# Access current item #}
  Index: {{ forloop.index }} {# Access loop variable #}
{% endfor %}

Outside: {{ item }}          {# Still accessible #}
```

Variables created before the loop are accessible inside:

```
{% assign prefix = "Item: " %}
{% for item in items %}
  {{ prefix }}{{ item }}     {# Can use prefix inside loop #}
{% endfor %}
```

### Conditional Scope

Variables assigned in if branches are accessible after:

```
{% if condition %}
  {% assign greeting = "Hello" %}
{% endif %}

{{ greeting }}               {# Still accessible #}
```

## Common Patterns

### Store Calculated Value

```
{% assign item_count = items | length %}
You have {{ item_count }} items
```

### Extract from List

```
{% assign first = items | first %}
{% assign last = items | last %}

First: {{ first }}, Last: {{ last }}
```

### Format Data

```
{% assign full_name = user.first_name | plus(" ") | plus(user.last_name) %}
Name: {{ full_name }}
```

### Store Filter Result

```
{% assign sorted = items | sort %}
{% assign unique = sorted | uniq %}
```

### Conditional Assignment

```
{% if user.admin %}
  {% assign role = "Administrator" %}
{% elsif user.moderator %}
  {% assign role = "Moderator" %}
{% else %}
  {% assign role = "User" %}
{% endif %}

Role: {{ role }}
```

### Build Strings

```
{% assign greeting = "Welcome, " %}
{% assign greeting = greeting | plus(user.name) %}
{{ greeting }}    # "Welcome, Alice"
```

## Accessing Context Variables

Special context variables available in some contexts:

### Loop Context in Map Directives

When using map directives with `#map` or `#filter`:

```
%{
  "users" => %{
    "#map" => [
      "{{$users}}",
      %{"name" => "{{$loop.item.name}}"}
    ]
  }
}
```

**Available loop variables:**
- `$loop.item` - Current item
- `$loop.index` - Current index
- `$loop.parentloop` - Parent loop (in nested maps)

### Workflow Variables

In workflow contexts, special variables are available:

```
{{ $input }}        # Workflow input
{{ $nodes }}        # Node execution results
{{ $variables }}    # Workflow variables
{{ $context }}      # Execution context
```

## Type Preservation

By default, all rendered output is a string. Use `preserve_types: true` to maintain types:

```elixir
Mau.render("{{ count }}", %{"count" => 42})
# {:ok, "42"}  <- String

Mau.render("{{ count }}", %{"count" => 42}, preserve_types: true)
# {:ok, 42}  <- Number
```

This preserves types for single-value templates. Mixed content always returns strings:

```
Mau.render("Count: {{ 42 }}", %{}, preserve_types: true)
# {:ok, "Count: 42"}  <- Still a string (mixed content)
```

## Variable Naming

Variable names can contain letters, numbers, and underscores:

```
{{ user }}
{{ user_name }}
{{ firstName }}    {# Also works, no special camelCase handling #}
{{ user_1 }}
```

### Reserved Names

Some names have special meaning and should be avoided:

```
{% for item in items %}
  {{ item }}       {# OK - loop variable #}
  {{ forloop }}    {# OK - loop metadata #}
{% endfor %}
```

## Safe Variable Access

### Check Before Access

```
{% if user %}
  Name: {{ user.name }}
{% endif %}
```

### Use Filters Safely

```
{% if items %}
  Count: {{ items | length }}
{% else %}
  No items
{% endif %}
```

### Provide Defaults

```
{% assign name = user.name %}
{% if name %}
  Hello {{ name }}
{% else %}
  Hello Guest
{% endif %}
```

## Examples

### Complete Profile Example

```
{% assign full_name = user.first_name | plus(" ") | plus(user.last_name) %}
{% assign user_count = users | length %}
{% assign is_admin = user.role == "admin" %}

Profile:
- Name: {{ full_name }}
- Email: {{ user.email }}
- Admin: {{ is_admin }}
- Total Users: {{ user_count }}
```

### List Processing

```
{% assign active_users = users | where("active") %}
{% assign user_names = active_users | map("name") | sort %}

Active users: {{ user_names | join(", ") }}
Count: {{ user_names | length }}
```

### Calculated Values

```
{% assign subtotal = 100 %}
{% assign tax_rate = 0.08 %}
{% assign tax = subtotal | times(tax_rate) | round(2) %}
{% assign total = subtotal | plus(tax) %}

Subtotal: ${{ subtotal }}
Tax: ${{ tax }}
Total: ${{ total }}
```

### Loop with Variables

```
{% assign colors = "red,green,blue" | split(",") %}

{% for color in colors %}
  {% assign index = forloop.index %}
  {% assign is_first = forloop.first %}

  {% if is_first %}First: {% endif %}{{ color }}
{% endfor %}
```

## Troubleshooting

### Variable Not Displaying

**Problem:** Variable renders as empty string

**Solutions:**
1. Check variable exists in context
2. Check variable name spelling
3. Use if statement to debug:

```
{% if user %}User defined{% else %}User undefined{% endif %}
{% if user.name %}Name defined{% else %}Name undefined{% endif %}
```

### Type Confusion

**Problem:** Numbers render as strings

**Solution:** Use `preserve_types: true` option

```elixir
Mau.render("{{ count }}", %{"count" => 42}, preserve_types: true)
```

### Scope Issues

**Problem:** Variable not accessible after assignment

**Solution:** Assign before use

```
{% assign var = "value" %}
{{ var }}    # Now available
```

## Summary

| Operation | Syntax | Example |
|-----------|--------|---------|
| Access | `{{ var }}` | `{{ user }}` |
| Property | `{{ obj.prop }}` | `{{ user.name }}` |
| Array | `{{ array[0] }}` | `{{ items[0] }}` |
| Assign | `{% assign x = y %}` | `{% assign name = "Alice" %}` |
| Expression | `{% assign x = y \| filter %}` | `{% assign count = items \| length %}` |
| Conditional | `{% if var %}` | Check if defined |

## See Also

- [Template Language Reference](../reference/template-language.md) - Variable syntax details
- [Filters Guide](filters.md) - Using filters with variables
- [Control Flow Guide](control-flow.md) - Using variables in conditionals