Skip to main content
LangWatch prompts use Liquid as their template language. Liquid gives you variables, conditionals, loops, and filters so you can build dynamic prompts without custom code. Templates are rendered when you call compile() in the Python or TypeScript SDK, or when the platform executes a prompt on your behalf.

Variables

Use double curly braces to insert dynamic values into your prompt.
Hello, {{ user_name }}! You have {{ message_count }} new messages.
When compiled with { user_name: "Alice", message_count: 3 }, this produces:
Hello, Alice! You have 3 new messages.

Accessing nested properties

Dot notation lets you reach into objects:
Customer: {{ user.name }} ({{ user.email }})

Available variables

The variables available depend on how you invoke the prompt:
ContextVariables
SDK compile()Any key-value pairs you pass in
Prompt PlaygroundValues set in the Variables tab
Optimization StudioNode inputs defined in the workflow
Scenario adaptersinput, messages, and adapter-specific fields

Conditionals

Control which parts of a prompt appear based on runtime values.

if / elsif / else

{% if tone == "formal" %}Dear {{ user_name }},{% elsif tone == "friendly" %}Hey {{ user_name }}!{% else %}Hello,{% endif %}

How can I help you today?
Compiled with { tone: "formal", user_name: "Dr. Smith" }:
Dear Dr. Smith,

How can I help you today?

unless

unless is the inverse of if — the block renders when the condition is false.
{% unless context %}Answer the question using only your general knowledge.{% endunless %}

Operators

OperatorMeaning
==Equal
!=Not equal
>Greater than
<Less than
>=Greater than or equal
<=Less than or equal
containsString contains substring, or array contains element
andBoth conditions true
orEither condition true
{% if input contains "refund" %}You handle refunds.{% else %}You are a general assistant.{% endif %}

Loops

Iterate over arrays with for.
Consider these topics:
{% for topic in topics %}- {{ topic }}
{% endfor %}
Compiled with { topics: ["AI safety", "Alignment", "Governance"] }:
Consider these topics:
- AI safety
- Alignment
- Governance

Comma-separated lists

Use forloop.last to avoid a trailing separator:
Topics: {% for item in topics %}{{ item }}{% unless forloop.last %}, {% endunless %}{% endfor %}
Produces: Topics: AI, ML, NLP

Loop variables

Inside a for block, these variables are available:
VariableDescription
forloop.indexCurrent iteration (1-based)
forloop.index0Current iteration (0-based)
forloop.firsttrue on the first iteration
forloop.lasttrue on the last iteration
forloop.lengthTotal number of items

Nested loops and conditionals

Loops and conditionals compose freely:
{% for user in users %}{% if user.active %}{{ user.name }}: {{ user.role }}
{% endif %}{% endfor %}

Filters

Filters transform a value. Chain them with the pipe character (|).
{{ name | upcase }}
{{ description | truncate: 50 }}
{{ tags | join: ", " }}

String filters

FilterExampleResult
upcase{{ "alice" | upcase }}ALICE
downcase{{ "HELLO" | downcase }}hello
capitalize{{ "hello world" | capitalize }}Hello world
truncate{{ "A long sentence" | truncate: 10 }}A long ...
strip{{ " hi " | strip }}hi
replace{{ "Hello" | replace: "Hello", "Hi" }}Hi
append{{ "hello" | append: " world" }}hello world
prepend{{ "world" | prepend: "hello " }}hello world
split{{ "a,b,c" | split: "," }}array ["a","b","c"]

Array filters

FilterExampleResult
join{{ tags | join: ", " }}ai, ml
first{{ items | first }}First element
last{{ items | last }}Last element
size{{ items | size }}Number of elements
sort{{ items | sort }}Sorted array
reverse{{ items | reverse }}Reversed array

Other filters

FilterExampleResult
default{{ name | default: "User" }}User when name is empty
json{{ data | json }}JSON-encoded string
Truncate behavior differs between SDKs. In the TypeScript SDK (LiquidJS), truncate: N outputs N characters of content then appends "...". In the Python SDK (python-liquid), truncate: N means N total characters including the "..." suffix. Keep this in mind if you rely on exact output length.

Assignment

Create local variables within the template using assign.
{% assign greeting = "Hello" %}{% assign formatted_name = name | capitalize %}{{ greeting }}, {{ formatted_name }}!
Compiled with { name: "alice" }:
Hello, Alice!
Assigned variables are local to the template — they do not need to be passed in and will not appear as required input variables.

Comments

Use comment tags for notes that should not appear in the rendered output.
{% comment %}
  This section is only included for premium users.
  TODO: add tier-based routing.
{% endcomment %}
{% if is_premium %}You have access to advanced features.{% endif %}

Practical examples

Conditional system prompt

Adapt the system message based on user input:
{% if input contains "refund" %}
You are a refund specialist. Follow the company refund policy strictly.
{% elsif input contains "billing" %}
You are a billing assistant. You can look up invoices and payment history.
{% else %}
You are a general customer support agent. Be helpful and concise.
{% endif %}

The customer said: {{ input }}

Few-shot examples from a list

Build few-shot examples dynamically:
You are a sentiment classifier. Classify the sentiment as positive, negative, or neutral.

{% for example in examples %}
Input: {{ example.text }}
Sentiment: {{ example.label }}
{% endfor %}
Input: {{ input }}
Sentiment:

Context-aware RAG prompt

Include retrieved context only when it exists:
{% if context %}Use the following context to answer the question:

{% for doc in context %}[{{ forloop.index }}] {{ doc }}
{% endfor %}
{% endif %}Question: {{ question }}

Multi-language greeting

{% if lang == "en" %}Hello{% elsif lang == "es" %}Hola{% elsif lang == "fr" %}Bonjour{% else %}Hi{% endif %}, {{ user_name }}!

Strict vs. lenient compilation

The SDKs offer two compilation modes:
  • compile() — Lenient. Undefined variables resolve to empty strings and missing conditions evaluate to false. Useful for optional fields.
  • compileStrict() (TypeScript) / compile_strict() (Python) — Strict. Throws a PromptCompilationError if any variable used in the template is not provided. Use this to catch typos and missing data early.
# Python - strict mode catches missing variables
prompt.compile_strict(name="Alice")  # raises PromptCompilationError if template uses {{ email }}
// TypeScript - strict mode catches missing variables
prompt.compileStrict({ name: "Alice" }); // throws PromptCompilationError if template uses {{ email }}

Further reading