Prompt Templates in OpenClaw
Prompt templates let you write prompts once and reuse them across tasks, agents, and projects. Variables, conditionals, and loops make templates flexible enough to handle real-world complexity.
What Are Prompt Templates?
A prompt template is a string with placeholders that get filled in at runtime. Templates promote reuse, make prompts version-controllable, and separate prompt logic from application code.
Jinja2-Based Template Syntax
OpenClaw uses Jinja2 for prompt templating. Install if needed: pip install jinja2
from jinja2 import Template
tpl = Template("""
You are a {{ role }}.
Task: {{ task }}
Constraints: return only JSON.
""")
prompt = tpl.render(role="data analyst", task="summarise the CSV")
Variable Injection
# Template file: prompts/summarise.j2
"""
Summarise the following {{ document_type }} in {{ max_words }} words or fewer.
Focus on: {{ focus_topics | join(", ") }}.
Document:
{{ document }}
"""
# Python usage
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("prompts/"))
tpl = env.get_template("summarise.j2")
prompt = tpl.render(
document_type="research paper",
max_words=150,
focus_topics=["methodology", "results"],
document=paper_text
)
Conditional Blocks
tpl = Template("""
Analyse the following code.
{% if include_security %}
Pay special attention to security vulnerabilities.
{% endif %}
{% if language %}
The code is written in {{ language }}.
{% endif %}
Code: {{ code }}
""")
Loops Over Lists
tpl = Template("""
Review the following items and provide feedback on each:
{% for item in items %}
{{ loop.index }}. {{ item }}
{% endfor %}
For each item, rate: quality (1-5) and suggest improvements.
""")
Nested Templates and Includes
# prompts/base.j2
{% include "prompts/role_section.j2" %}
{% include "prompts/task_section.j2" %}
{% block constraints %}
Return valid JSON only.
{% endblock %}
# prompts/analyst.j2
{% extends "prompts/base.j2" %}
{% block constraints %}
Return JSON: {"insights": list, "anomalies": list}
{% endblock %}
Managing a Prompt Library (YAML Promptbook)
# prompts/library.yaml
prompts:
summarise:
version: "2.1"
template: |
Summarise: {{ text }} in {{ words }} words.
classify:
version: "1.0"
template: |
Classify as positive/negative/neutral: {{ text }}
import yaml
from jinja2 import Template
with open("prompts/library.yaml") as f:
library = yaml.safe_load(f)
def get_prompt(name: str, **kwargs) -> str:
tpl = Template(library["prompts"][name]["template"])
return tpl.render(**kwargs)
Error Handling in Templates
Jinja2 rendering fails silently by default when a variable is missing — it renders as an empty string, which produces invalid prompts. Enable StrictUndefined in production to surface missing variables immediately.
from jinja2 import Environment, StrictUndefined, FileSystemLoader
env = Environment(
loader=FileSystemLoader("prompts/"),
undefined=StrictUndefined, # raise instead of silently inserting ''
autoescape=False, # prompts are not HTML
)
def render_prompt(template_name: str, **kwargs) -> str:
try:
return env.get_template(template_name).render(**kwargs)
except Exception as exc:
# Log the missing variable name, fall back to safe minimal prompt
logger.error("Template render error in %s: %s", template_name, exc)
raise PromptBuildError(f"Failed to render {template_name}") from exc
Template Security
Prompt templates can become injection vectors when user-supplied values are inserted verbatim. Always sanitize dynamic values before rendering:
- Strip control characters. Remove null bytes and control characters from all user inputs:
text.translate({0:None, **{c:None for c in range(1,32)}}). - Limit length. Enforce a maximum length on every user-supplied variable before inserting it into a template. A 10,000-token user input can hijack a carefully crafted system prompt.
- Never use
evalin templates. Jinja2's{% raw %}{% set %}{% endraw %}and filters are safe; Python-eval-based template engines are not. Do not pass user data toeval()orexec()inside templates. - Separate trust levels. Render system-prompt templates and user-content inserts with different variable namespaces so user data cannot overwrite a system variable.
Testing Templates
Templates should be tested without making real LLM calls — verify rendering correctness and variable injection independently:
import pytest
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("prompts/"))
def render(name, **kw):
return env.get_template(name).render(**kw)
def test_coding_template_includes_task():
result = render("coding-agent.j2", task="write tests", language="Python")
assert "write tests" in result
assert "Python" in result
def test_coding_template_no_empty_placeholders():
result = render("coding-agent.j2", task="x", language="Go")
assert "{{" not in result # no un-rendered Jinja variables
def test_report_template_formats_date():
result = render("report.j2", report_date="2026-03-17", period="Q1")
assert "2026-03-17" in result
assert "Q1" in result