Skip to the content.

Jinja2 to Luma Migration Guide

Overview

Luma provides full Jinja2 compatibility to enable seamless migration from existing Jinja2 templates. While Jinja2 syntax is fully supported, we strongly recommend using Luma’s cleaner native syntax for better readability and maintainability.

Why Migrate?

Aspect Jinja2 Luma
Syntax Noise Heavy: {{ }}, {% %} Light: $var, @if
Readability More cluttered Cleaner, shell-like
Whitespace Often needs {%- / -%} control Smart preservation by default
All File Types YAML struggles without trim Works perfectly everywhere
Escaping Complex rules Simple $$ for literal $

Comparison Examples

Variable Interpolation:

{# Jinja2 #}
Hello {{ user.name }}!
@# Luma
Hello $user.name!

Control Flow:

{# Jinja2 #}
{% if user.is_admin %}
  <p>Welcome, admin {{ user.name }}!</p>
{% else %}
  <p>Welcome, {{ user.name }}!</p>
{% endif %}
@# Luma
@if user.is_admin
  <p>Welcome, admin $user.name!</p>
@else
  <p>Welcome, $user.name!</p>
@end

Loops:

{# Jinja2 #}
{% for item in items %}
  <li>{{ loop.index }}: {{ item.name }}</li>
{% endfor %}
@# Luma
@for item in items
  <li>${loop.index}: $item.name</li>
@end

Automatic Migration Tool

Luma includes a built-in migration tool that converts Jinja2 templates to native Luma syntax automatically.

Installation

# After installing Luma
luarocks install luma
# or use directly from source

Usage

# Convert and print to stdout
luma migrate template.jinja

# Convert and save
luma migrate template.jinja > template.luma

# Convert in-place
luma migrate template.jinja --in-place

# Convert entire directory
luma migrate templates/ --output luma-pys/

# Preview changes with diff
luma migrate template.jinja --dry-run --diff

Conversion Table

Jinja2 Syntax Luma Syntax
&#123;&#123; expr &#125;&#125; $&#123;expr&#125;
&#123;&#123; var &#125;&#125; $var (simplified)
&#123;&#123; user.name &#125;&#125; $user.name
&#123;% if x %&#125; @if x
&#123;% elif x %&#125; @elif x
&#123;% else %&#125; @else
&#123;% endif %&#125; @end
&#123;% for x in y %&#125; @for x in y
&#123;% endfor %&#125; @end
&#123;% set x = y %&#125; @let x = y
&#123;% macro name() %&#125; @macro name()
&#123;% endmacro %&#125; @end
&#123;% call name() %&#125; @call name()
&#123;% endcall %&#125; @end
&#123;% include "x" %&#125; @include "x"
&#123;% extends "x" %&#125; @extends "x"
&#123;% block name %&#125; @block name
&#123;% endblock %&#125; @end
&#123;% break %&#125; @break
&#123;% continue %&#125; @continue
&#123;# comment #&#125; @# comment

Feature Compatibility Matrix

✅ Fully Supported

These Jinja2 features work identically in both syntaxes:

🚧 Pending (On Roadmap)

These features will be added for full Jinja2 parity:


Migration Workflow

1. Automatic Conversion

Start with the automated migration tool:

# Backup your templates first!
cp -r templates templates.backup

# Migrate entire directory
luma migrate templates/ --output luma-pys/

2. Review & Test

The converter is AST-based and accurate, but always review:

3. Gradual Migration

You can migrate incrementally:

4. Suppress Warnings

During migration, suppress deprecation warnings:

-- In code
luma.render(template, context, { no_jinja_warning = true })
# Via environment variable
export LUMA_NO_JINJA_WARNING=1

Framework Integration

Flask

# Before (Jinja2)
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html.jinja', name='World')
# After (Luma)
from flask import Flask
from luma.contrib.flask import Luma

app = Flask(__name__)
app.jinja_env = Luma()  # Drop-in replacement

@app.route('/')
def index():
    return render_template('index.html.luma', name='World')

Django

# settings.py
TEMPLATES = [{
    'BACKEND': 'luma.contrib.django.LumaTemplates',
    'DIRS': ['templates'],
    'OPTIONS': {
        'no_jinja_warning': True,  # Suppress during migration
    }
}]

Ansible

# playbook.yml
- name: Deploy config
  template:
    src: config.luma  # Use .luma extension
    dest: /etc/app/config.yaml
    engine: luma

Common Patterns

Pattern 1: Kubernetes Manifests

Before (Jinja2):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ app_name }}
spec:
  replicas: {{ replicas | default(3) }}
  template:
    spec:
      containers:
      {% for container in containers %}
        - name: {{ container.name }}
          image: {{ container.image }}:{{ container.tag | default('latest') }}
          {% if container.env %}
          env:
            {% for key, value in container.env.items() %}
            - name: {{ key }}
              value: "{{ value }}"
            {% endfor %}
          {% endif %}
      {% endfor %}

After (Luma):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: $app_name
spec:
  replicas: ${replicas | default(3)}
  template:
    spec:
      containers:
      @for container in containers
        - name: $container.name
          image: ${container.image}:${container.tag | default("latest")}
          @if container.env
          env:
            @for key, value in container.env
            - name: $key
              value: "$value"
            @end
          @end
      @end

Pattern 2: HTML Components

Before (Jinja2):

{% macro button(text, type='primary') %}
  <button class="btn btn-{{ type }}">
    {{ text | capitalize }}
  </button>
{% endmacro %}

{{ button('submit', type='success') }}

After (Luma):

@macro button(text, type='primary')
  <button class="btn btn-$type">
    ${text | capitalize}
  </button>
@end

@call button('submit', type='success')

Troubleshooting

Issue: Templates not rendering

Solution: Check syntax detection:

-- Explicitly set syntax during transition
luma.render(template, context, { syntax = "jinja" })
-- or
luma.render(template, context, { syntax = "luma" })

Issue: Filter not found

Solution: Ensure all custom filters are registered:

luma.register_filter("custom_filter", function(value)
    return transform(value)
end)

Issue: Whitespace issues in YAML

Solution: Use proper indentation with directives:

# Good - directives aligned with content
spec:
  containers:
  @for container in containers
    - name: $container.name
  @end

# Avoid - directives at column 0
spec:
  containers:
@for container in containers
    - name: $container.name
@end

Getting Help

Next Steps

  1. ✅ Review the conversion table above
  2. ✅ Run luma migrate on a test template
  3. ✅ Compare output and verify correctness
  4. ✅ Gradually migrate your project
  5. ✅ Enjoy cleaner, more maintainable templates!