Skip to main content

What is Liquid Templating?

Liquid is a template language that lets you combine static text with dynamic data. In Slate workflows, you can use Liquid templating in:
  • Text blocks - Format and combine data from previous steps
  • LLM blocks - Build dynamic prompts with workflow data
Think of Liquid as a way to write templates with placeholders that get replaced with actual data when the workflow runs.

Where Can I Use Liquid?

Text Block

Use Liquid to format workflow data into readable text, reports, or structured output. Example:
# Keyword Report

Keyword: {{step_1.output.Keyword}}
Volume: {{step_1.output.Search_Volume}}

LLM Block

Use Liquid in prompts to include dynamic data from previous workflow steps. Example:
Write a blog post about {{step_1.output.topic}}.

Target these keywords: {{step_2.output | map: "Keyword" | join: ", "}}

The post should be {{step_3.output.word_count}} words long.

Basic Syntax

Output Tags

Use {{...}} to output data. Basic output:
{{step_1.output}}
Access object properties:
{{step_2.output.title}}
{{step_2.output.keyword}}
{{step_2.output.search_volume}}
Access array items:
{{step_3.output[0]}}
{{step_3.output[1].keyword}}

Logic Tags

Use {%...%} for control flow and logic. Examples:
{% if condition %}
  Show this
{% endif %}

{% for item in array %}
  {{item}}
{% endfor %}

Filters

Filters modify output values. Use the pipe | symbol to chain filters.

String Filters

Uppercase:
{{step_1.output | upcase}}
Input: "hello world" → Output: "HELLO WORLD" Lowercase:
{{step_1.output | downcase}}
Input: "HELLO WORLD" → Output: "hello world" Capitalize:
{{step_1.output | capitalize}}
Input: "hello world" → Output: "Hello world" Strip whitespace:
{{step_1.output | strip}}
Input: " hello " → Output: "hello" Replace:
{{step_1.output | replace: "old", "new"}}
Input: "old text" → Output: "new text" Remove:
{{step_1.output | remove: "word"}}
Input: "remove word here" → Output: "remove here" Truncate:
{{step_1.output | truncate: 20}}
Input: "This is a long sentence" → Output: "This is a long se..." Split into array:
{{step_1.output | split: ","}}
Input: "a,b,c" → Output: ["a", "b", "c"] Join array:
{{step_1.output | join: ", "}}
Input: ["a", "b", "c"] → Output: "a, b, c" Append:
{{step_1.output | append: " - suffix"}}
Input: "text" → Output: "text - suffix" Prepend:
{{step_1.output | prepend: "prefix - "}}
Input: "text" → Output: "prefix - text" Slice (substring):
{{step_1.output | slice: 0, 5}}
Input: "hello world" → Output: "hello" Strip HTML:
{{step_1.output | strip_html}}
Input: "<p>text</p>" → Output: "text" URL encode:
{{step_1.output | url_encode}}
Input: "hello world" → Output: "hello%20world" Escape:
{{step_1.output | escape}}
Input: "<html>" → Output: "&lt;html&gt;"

Number Filters

Plus:
{{step_1.output | plus: 10}}
Input: 5 → Output: 15 Minus:
{{step_1.output | minus: 3}}
Input: 10 → Output: 7 Times:
{{step_1.output | times: 2}}
Input: 5 → Output: 10 Divided by:
{{step_1.output | divided_by: 2}}
Input: 10 → Output: 5 Modulo:
{{step_1.output | modulo: 3}}
Input: 10 → Output: 1 Round:
{{step_1.output | round: 2}}
Input: 3.14159 → Output: 3.14 Ceil:
{{step_1.output | ceil}}
Input: 3.2 → Output: 4 Floor:
{{step_1.output | floor}}
Input: 3.9 → Output: 3

Array Filters

Size (length):
{{step_1.output | size}}
Input: ["a", "b", "c"] → Output: 3 First:
{{step_1.output | first}}
Input: ["a", "b", "c"] → Output: "a" Last:
{{step_1.output | last}}
Input: ["a", "b", "c"] → Output: "c" Join:
{{step_1.output | join: " | "}}
Input: ["a", "b", "c"] → Output: "a | b | c" Reverse:
{{step_1.output | reverse}}
Input: ["a", "b", "c"] → Output: ["c", "b", "a"] Sort:
{{step_1.output | sort}}
Input: ["c", "a", "b"] → Output: ["a", "b", "c"] Uniq (unique):
{{step_1.output | uniq}}
Input: ["a", "b", "a", "c"] → Output: ["a", "b", "c"] Map (extract property):
{{step_1.output | map: "keyword"}}
Input: [{"keyword": "crm"}, {"keyword": "sales"}] → Output: ["crm", "sales"] Where (filter by property):
{{step_1.output | where: "status", "active"}}
Input: [{"name": "a", "status": "active"}, {"name": "b", "status": "inactive"}] → Output: [{"name": "a", "status": "active"}]

Chaining Filters

You can chain multiple filters together:
{{step_1.output | downcase | replace: " ", "-" | truncate: 20}}
{{step_2.output | split: "," | sort | join: " | "}}
{{step_3.output | map: "keyword" | join: ", " | upcase}}

Control Flow

If Statements

Basic if:
{% if step_1.output == "approved" %}
Content is approved
{% endif %}
Input: step_1.output = "approved" Output:
Content is approved
Input: step_1.output = "rejected" Output: (empty)
If-else:
{% if step_1.output.volume > 10000 %}
High volume keyword
{% else %}
Low volume keyword
{% endif %}
Input: step_1.output.volume = 15000 Output:
High volume keyword
Input: step_1.output.volume = 5000 Output:
Low volume keyword

If-elsif-else:
{% if step_1.output.volume > 50000 %}
Very high volume
{% elsif step_1.output.volume > 10000 %}
High volume
{% else %}
Low volume
{% endif %}
Input: step_1.output.volume = 75000 Output:
Very high volume
Input: step_1.output.volume = 25000 Output:
High volume
Input: step_1.output.volume = 5000 Output:
Low volume

Multiple conditions with and:
{% if step_1.output.volume > 1000 and step_1.output.difficulty < 50 %}
Good opportunity
{% endif %}
Input: step_1.output.volume = 5000 and step_1.output.difficulty = 30 Output:
Good opportunity
Input: step_1.output.volume = 500 or step_1.output.difficulty = 60 Output: (empty)
Multiple conditions with or:
{% if step_1.output.status == "active" or step_1.output.status == "pending" %}
Show this content
{% endif %}
Input: step_1.output.status = "active" Output:
Show this content
Input: step_1.output.status = "pending" Output:
Show this content
Input: step_1.output.status = "inactive" Output: (empty)

For Loops

Basic loop:
{% for item in step_1.output %}
- {{item}}
{% endfor %}
Input: step_1.output = ["crm", "sales", "marketing"] Output:
- crm
- sales
- marketing

Loop with index:
{% for item in step_1.output %}
{{forloop.index}}. {{item}}
{% endfor %}
Input: step_1.output = ["crm", "sales", "marketing"] Output:
1. crm
2. sales
3. marketing

Access object properties in loop:
{% for keyword in step_1.output %}
Keyword: {{keyword.Keyword}}
Volume: {{keyword.Search_Volume}}
---
{% endfor %}
Input:
[
  {"Keyword": "crm", "Search_Volume": "165000"},
  {"Keyword": "sales", "Search_Volume": "74000"}
]
Output:
Keyword: crm
Volume: 165000
---
Keyword: sales
Volume: 74000
---

Loop with conditions:
{% for keyword in step_1.output %}
  {% if keyword.Search_Volume > 10000 %}
- {{keyword.Keyword}} ({{keyword.Search_Volume}} searches)
  {% endif %}
{% endfor %}
Input:
[
  {"Keyword": "crm", "Search_Volume": "165000"},
  {"Keyword": "email", "Search_Volume": "5400"},
  {"Keyword": "marketing", "Search_Volume": "74000"}
]
Output:
- crm (165000 searches)
- marketing (74000 searches)

Loop variables:
  • forloop.index - Current iteration (1-indexed)
  • forloop.index0 - Current iteration (0-indexed)
  • forloop.first - True if first iteration
  • forloop.last - True if last iteration
  • forloop.length - Total number of iterations
Example with loop variables:
{% for item in step_1.output %}
  {% if forloop.first %}
=== Top Results ===
  {% endif %}

{{forloop.index}}. {{item.title}}

  {% if forloop.last %}
=== End of Results ===
  {% endif %}
{% endfor %}
Input:
[
  {"title": "Article 1"},
  {"title": "Article 2"},
  {"title": "Article 3"}
]
Output:
=== Top Results ===

1. Article 1

2. Article 2

3. Article 3

=== End of Results ===

Limit iterations:
{% for item in step_1.output limit:5 %}
{{item}}
{% endfor %}
Input: step_1.output = ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8"] Output:
item1
item2
item3
item4
item5

Skip items (offset):
{% for item in step_1.output offset:3 %}
{{item}}
{% endfor %}
Input: step_1.output = ["item1", "item2", "item3", "item4", "item5"] Output:
item4
item5

Unless (Negative If)

{% unless step_1.output.status == "error" %}
Processing successful
{% endunless %}
Input: step_1.output.status = "success" Output:
Processing successful
Input: step_1.output.status = "error" Output: (empty)

Case/When (Switch Statement)

{% case step_1.output.status %}
  {% when "approved" %}
    Approved
  {% when "pending" %}
    Pending Review
  {% when "rejected" %}
    Rejected
  {% else %}
    Unknown Status
{% endcase %}
Input: step_1.output.status = "approved" Output:
Approved
Input: step_1.output.status = "pending" Output:
Pending Review
Input: step_1.output.status = "rejected" Output:
Rejected
Input: step_1.output.status = "unknown" Output:
Unknown Status

Variables

Assign

Create variables to reuse values:
{% assign keyword_count = step_1.output | size %}
{% assign first_keyword = step_1.output | first %}

Total keywords: {{keyword_count}}
First keyword: {{first_keyword}}
Use case:
{% assign volume = step_1.output.Search_Volume | plus: 0 %}
{% if volume > 10000 %}
High volume: {{volume}}
{% endif %}

Capture

Capture multi-line content into a variable:
{% capture summary %}
Keywords analyzed: {{step_1.output | size}}
Top keyword: {{step_1.output[0].Keyword}}
Average volume: {{step_1.output | map: "Search_Volume" | avg}}
{% endcapture %}

{{summary}}

Comments

Single line comment:
{% comment %}This is a comment{% endcomment %}
Multi-line comment:
{% comment %}
This is a longer comment
that spans multiple lines
{% endcomment %}

Whitespace Control

Remove whitespace before:
{%- if condition -%}
Remove whitespace after:
{% for item in array -%}
{{item}}
{% endfor %}
Example:
{% for item in step_1.output -%}
{{item}},
{% endfor %}
Without -: "a, b, c, " With -: "a,b,c,"

Common Patterns

Extract Keywords from Array

{% assign keywords = step_1.output | map: "Keyword" | join: ", " %}

Target keywords: {{keywords}}

Format Numbers

{% assign volume = step_1.output.Search_Volume | plus: 0 %}
{% assign formatted_volume = volume | divided_by: 1000 %}

Search volume: {{formatted_volume}}K searches/month

Conditional Formatting

{% assign difficulty = step_1.output.Keyword_Difficulty_Index | plus: 0 %}

Difficulty: {{difficulty}}/100
{% if difficulty > 70 %}
Status: Very Difficult
{% elsif difficulty > 50 %}
Status: Moderate
{% else %}
Status: Easy
{% endif %}

Build Lists

{% for keyword in step_1.output %}
  {% if forloop.index <= 5 %}
{{forloop.index}}. {{keyword.Keyword}} ({{keyword.Search_Volume}} searches)
  {% endif %}
{% endfor %}

Combine Multiple Steps

# Analysis Report

## Keywords
{% for kw in step_1.output limit:3 %}
- {{kw.Keyword}}: {{kw.Search_Volume}} searches
{% endfor %}

## Domain
Domain: {{step_2.output.Domain}}
Traffic: {{step_2.output.Organic_Traffic}}

## Summary
{{step_3.output}}

Use Cases

Use Case 1: Dynamic LLM Prompts

In LLM block prompt:
Write a blog post about {{step_1.output.topic}}.

Target these keywords:
{% for kw in step_2.output limit:5 %}
- {{kw.Keyword}} ({{kw.Search_Volume}} searches)
{% endfor %}

The post should:
- Be {{step_3.output.word_count}} words long
- Target audience: {{step_3.output.audience}}
- Tone: {{step_3.output.tone}}

{% if step_1.output.include_examples %}
Include at least 3 real-world examples.
{% endif %}

Use Case 2: Format Data for Reports

In Text block:
# SEO Keyword Report

Generated: {{step_1.output.date}}

## Top Keywords

{% for keyword in step_2.output %}
### {{forloop.index}}. {{keyword.Keyword}}

- Search Volume: {{keyword.Search_Volume}}
- Difficulty: {{keyword.Keyword_Difficulty_Index}}/100
- CPC: ${{keyword.CPC}}
- Opportunity: {% if keyword.Keyword_Difficulty_Index < 50 %}High{% else %}Medium{% endif %}

{% endfor %}

Total keywords analyzed: {{step_2.output | size}}

Use Case 3: Build Structured Output

In Text block:
{% assign keywords = step_1.output | map: "Keyword" | join: ", " %}
{% assign total_volume = step_1.output | map: "Search_Volume" | sum %}
{% assign avg_difficulty = step_1.output | map: "Keyword_Difficulty_Index" | avg %}

Summary:
- Keywords: {{keywords}}
- Total Search Volume: {{total_volume}}
- Average Difficulty: {{avg_difficulty | round: 1}}

Best Practices

Template Design

  • Keep it readable: Use line breaks and indentation
  • Test incrementally: Build complex templates step by step
  • Comment complex logic: Use {% comment %} for clarity
  • Handle missing data: Use conditionals to check for data

Filter Usage

  • Chain wisely: Too many filters can be hard to debug
  • Convert types: Use | plus: 0 to convert strings to numbers
  • Extract early: Use map before join for cleaner code
  • Filter before loop: Process data before iteration when possible

Control Flow

  • Avoid deep nesting: Keep if/for logic simple
  • Use elsif: Better than multiple nested ifs
  • Check array length: Use {% if array.size > 0 %} before looping
  • Limit iterations: Use limit: for large arrays

Troubleshooting

Output is Blank

Problem: Template shows no output. Solution:
  • Check step references: {{step_X.output}}
  • Verify previous steps completed successfully
  • Test with simple output: {{step_1.output}}

Filter Not Working

Problem: Filter produces unexpected results. Solution:
  • Check filter syntax: {{value | filter: "param"}}
  • Ensure data type matches filter (string/number/array)
  • Chain filters one at a time to debug

Property Access Error

Problem: {{step_1.output.keyword}} shows blank. Solution:
  • Check exact property name (case-sensitive)
  • Verify object structure: {{step_1.output}}
  • Use array index if needed: {{step_1.output[0].keyword}}

Loop Not Working

Problem: For loop doesn’t produce output. Solution:
  • Verify input is an array
  • Check array isn’t empty: {{step_1.output | size}}
  • Ensure correct loop syntax: {% for item in step_1.output %}

Condition Not Working

Problem: If statement doesn’t behave as expected. Solution:
  • Check comparison operators: ==, >, <, >=, <=, !=
  • Convert strings to numbers: {% assign num = value | plus: 0 %}
  • Use and/or for multiple conditions

What’s Next

Now that you understand Liquid templating: