Skip to main content

Template Functions (J2/Jinja2)

Render Jinja2-compatible templates with data. These functions enable workflows to transform structured data into formatted text using loops, conditionals, filters, and variable substitution.

Overview

The template integration provides two functions powered by Nunjucks (Mozilla's JavaScript implementation of Jinja2):

  • renderTemplate() - Render a template with explicit data
  • renderTemplateFromContext() - Render using all available context variables

Why Use Templates?

Templates are ideal for:

  • Formatting LLM responses into human-readable text
  • Building social media posts from structured data
  • Creating notifications with dynamic content
  • Generating reports from arrays of data
  • Conditional formatting based on data values

Functions

renderTemplate

Render a Jinja2/Nunjucks template string with provided data.

Signature:

async function renderTemplate(
template: string,
data: object
): Promise<string>

Parameters:

ParameterTypeDescription
templatestringJ2 template string with variables and control structures
dataobjectData object for template variable substitution

Returns: Rendered string output

Example:

const template = "Hello {{ name }}! You have {{ count }} messages.";
const result = await renderTemplate(template, { name: "David", count: 5 });
// Result: "Hello David! You have 5 messages."

renderTemplateFromContext

Render a template using all available variables from the workflow context.

Signature:

async function renderTemplateFromContext(
template: string
): Promise<string>

Available Context Variables:

VariableDescription
Workflow fieldsSame names as script globals (type, content, user, payload, …)
latestPromptResponseRaw response from the last Prompt entity
promptDataParsed JSON from the last Prompt entity (if valid JSON)
stmShort-term memory object
{variableName}All variables from Admin Console Settings

Example:

// Assumes a Prompt entity returned JSON with teams array
const template = `
{% for team in promptData.teams %}
- {{ team.name }}
{% endfor %}
`;
const result = await renderTemplateFromContext(template);

Template Syntax Reference

Variables

Access data using double curly braces:

const template = `
Name: {{ user.name }}
Email: {{ user.email }}
Role: {{ user.profile.role }}
First item: {{ items[0] }}
`;

Conditionals

Use {% if %}, {% elif %}, {% else %}:

const template = `
{% if score >= 90 %}
Grade: A
{% elif score >= 80 %}
Grade: B
{% elif score >= 70 %}
Grade: C
{% else %}
Grade: F
{% endif %}
`;

Loops

Iterate over arrays with {% for %}:

const template = `
{% for player in players %}
{{ loop.index }}. {{ player.name }} - {{ player.position }}
{% endfor %}
`;

Loop Variables:

VariableDescription
loop.indexCurrent iteration (1-indexed)
loop.index0Current iteration (0-indexed)
loop.firstTrue if first iteration
loop.lastTrue if last iteration
loop.lengthTotal number of items

Filters

Transform values with pipe syntax:

const template = `
{{ name | upper }} // JOHN
{{ name | lower }} // john
{{ name | capitalize }} // John
{{ items | length }} // 5
{{ items | first }} // First item
{{ items | last }} // Last item
{{ items | join(", ") }} // a, b, c
{{ value | default("N/A") }} // Fallback if undefined
{{ text | trim }} // Remove whitespace
{{ items | sort }} // Sort array
{{ number | round(2) }} // Round to 2 decimals
`;

Comments

Add non-rendered comments:

const template = `
{# This is a comment and won't appear in output #}
{{ name }}
`;

Complete Examples

Example 1: Format Sports Data for Social Media

Transform structured LLM output into a Mastodon-ready post:

Prompt Entity Output (JSON):

{
"teams": [
{ "name": "Ravens", "market": "Baltimore", "wins": 10, "losses": 3, "playoffs": true },
{ "name": "Chiefs", "market": "Kansas City", "wins": 9, "losses": 4, "playoffs": true }
],
"summary": "AFC playoff race heating up!"
}

Action Script:

// Get and parse the LLM response
const response = await latestPromptResponse();
const data = JSON.parse(response.replace(/'/g, '"'));

// Define template for Mastodon post
const template = `
🏈 NFL Update: {{ summary }}

{% for team in teams %}
{{ team.market }} {{ team.name }}: {{ team.wins }}-{{ team.losses }}{% if team.playoffs %} ✅{% endif %}
{% endfor %}
#NFL #Football #Sports
`;

// Render and post
const formatted = await renderTemplate(template, data);
await postToMastodon(MASTODON_URL, MASTODON_ACCESS_TOKEN, formatted.trim());

print('Posted to Mastodon!');

Output:

🏈 NFL Update: AFC playoff race heating up!

Baltimore Ravens: 10-3 ✅
Kansas City Chiefs: 9-4 ✅

#NFL #Football #Sports

Example 2: Build Dynamic Notification

Action Script:

const template = `
{% if priority == "high" %}
🚨 URGENT: {{ title | upper }}
{% else %}
📢 {{ title }}
{% endif %}

{{ body }}

{% if actions and actions | length > 0 %}
Actions:
{% for action in actions %}
- {{ action }}
{% endfor %}
{% endif %}

Sent: {{ timestamp | default("now") }}
`;

const result = await renderTemplateFromContext(template);
print(result);

Example 3: Generate Report from Array

Action Script:

const data = {
title: "Weekly Sales Report",
date: "2026-01-20",
items: [
{ product: "Widget A", quantity: 150, revenue: 4500 },
{ product: "Widget B", quantity: 89, revenue: 2670 },
{ product: "Widget C", quantity: 234, revenue: 7020 }
],
totalRevenue: 14190
};

const template = `
📊 {{ title }}
Date: {{ date }}
${"=".repeat(40)}

{% for item in items %}
{{ loop.index }}. {{ item.product }}
Qty: {{ item.quantity }} | Revenue: ${{ item.revenue }}
{% endfor %}

${"=".repeat(40)}
💰 Total Revenue: ${{ totalRevenue }}
`;

const report = await renderTemplate(template, data);
print(report);

Example 4: Conditional Content with Fallbacks

Action Script:

const template = `
Player: {{ player.name | default("Unknown") }}
Team: {{ player.team | default("Free Agent") }}
Position: {{ player.position | default("N/A") }}

{% if player.stats %}
Stats:
- Touchdowns: {{ player.stats.touchdowns | default(0) }}
- Yards: {{ player.stats.yards | default(0) }}
{% else %}
Stats not available.
{% endif %}

{% if player.injured %}
⚠️ Currently on injured reserve
{% endif %}
`;

const result = await renderTemplate(template, {
player: {
name: "Lamar Jackson",
team: "Baltimore Ravens",
position: "QB",
stats: { touchdowns: 24, yards: 3678 }
}
});
print(result);

Example 5: Using Context Variables

Use all available context without passing explicit data:

Action Script:

// This template uses automatic context variables
const template = `
Processing message from: {{ source | default("unknown") }}
Organization: {{ ORGANIZATION_NAME | default("N/A") }}

{% if promptData %}
AI Response Summary:
{{ promptData.summary | default(latestPromptResponse) }}
{% endif %}

{% if stm.lastProcessed %}
Previous: {{ stm.lastProcessed }}
{% endif %}
`;

const result = await renderTemplateFromContext(template);
print(result);

// Store for next message
stmSet('lastProcessed', new Date().toISOString());

Example 6: Nested Data Structures

Action Script:

const data = {
tournament: {
name: "AFC Championship",
teams: {
home: { name: "Ravens", city: "Baltimore", seed: 1 },
away: { name: "Bills", city: "Buffalo", seed: 2 }
},
venue: {
name: "M&T Bank Stadium",
city: "Baltimore",
capacity: 71008
}
}
};

const template = `
🏟️ {{ tournament.name }}

{{ tournament.teams.away.city }} {{ tournament.teams.away.name }} (#{{ tournament.teams.away.seed }})
@
{{ tournament.teams.home.city }} {{ tournament.teams.home.name }} (#{{ tournament.teams.home.seed }})

📍 {{ tournament.venue.name }}
{{ tournament.venue.city }} ({{ tournament.venue.capacity | string }} capacity)
`;

const result = await renderTemplate(template, data);
print(result);

Example 7: Combining with LLM Output

A complete workflow pattern:

Prompt Entity:

Analyze this game event and return JSON with:
- summary: one sentence summary
- sentiment: positive/negative/neutral
- highlights: array of key points

Event: {{event}}

Action Script (following the Prompt):

// Parse the LLM response
const response = await latestPromptResponse();
let analysis;
try {
analysis = JSON.parse(response.replace(/'/g, '"'));
} catch (e) {
print('Failed to parse LLM response:', e.message);
return;
}

// Format for different outputs
const socialTemplate = `
{{ summary }}

{% for highlight in highlights %}
• {{ highlight }}
{% endfor %}

#GameDay
`;

const notificationTemplate = `
{% if sentiment == "positive" %}🎉{% elif sentiment == "negative" %}😔{% else %}📢{% endif %} {{ summary }}
`;

// Generate both
const socialPost = await renderTemplate(socialTemplate, analysis);
const notification = await renderTemplate(notificationTemplate, analysis);

// Use them
await postToMastodon(MASTODON_URL, MASTODON_ACCESS_TOKEN, socialPost.trim());
print('Notification:', notification);

// Pass to next entity
output = { socialPost, notification, sentiment: analysis.sentiment };

Tips & Best Practices

  1. Trim output - Use .trim() on results to remove extra whitespace
  2. Use default filter - Prevent undefined errors with {{ value | default("fallback") }}
  3. Parse LLM output - Remember to JSON.parse() the prompt response before passing to template
  4. Handle single quotes - LLMs sometimes return Python-style single quotes; use response.replace(/'/g, '"') before parsing
  5. Test templates - Use print() to verify output before posting to external services
  6. Keep templates readable - Use multi-line template literals for complex templates

Troubleshooting

"Template rendering failed"

Cause: Syntax error in template

Solution:

  • Check for unclosed tags ({% if %} without {% endif %})
  • Verify variable names match your data structure
  • Check filter syntax (e.g., | join(",") not | join ,)

Undefined variables appearing as empty

Cause: Variable path doesn't exist in data

Solution:

  • Use default filter: {{ value | default("N/A") }}
  • Check data structure with print(JSON.stringify(data))

Template output includes raw template syntax

Cause: Template not being rendered (likely a typo in function name)

Solution:

  • Ensure you're using await renderTemplate() or await renderTemplateFromContext()
  • Check that the template string is passed correctly