Variables & templating
Declared variables with optional defaults, message-list placeholders, and the strict Mustache-subset syntax that fills them. Compile always succeeds with whatever you pass; a missing value falls back to the default, or empty.
Most prompt registries treat a variable as whatever a regex happened to find in the text. Trodo declares each variable — so it knows the full set the prompt uses, can give each an optional default, and can catch a value you pass for a variable that doesn't exist.
Declaring variables
Write {{name}} in any message and it's detected on the Variables tab — the name is all the engine needs, and you never type it. The one thing you set is an optional default:
| Property | Meaning |
|---|---|
| Name | Auto-detected from the messages. Rename the {{token}} in the message and the declaration follows — it's not edited here. |
| Default | Used when the caller omits the variable. If there's no default, an omitted variable renders empty. |
Detection and the stored declarations are reconciled on save, server-side, so the declared list always matches what the messages actually reference.
There's no "required" flag — nothing is required, and compile always succeeds with whatever it's given (missing → default, or empty). A variable's type is inferred from how you use it, never set by hand: a plain {{name}} is a scalar (rendered as text), a placeholder is a messages list, and a name used as a section head {{#items}} is json — you pass it a list or object and the section iterates over it. See Template syntax.
Passing values
Pass values to compile(). Python also accepts them as keyword arguments.
const { messages } = prompt.compile({
company: 'Acme',
question: 'where is my order?',
// any variable you omit falls back to its default, or empty
});compiled = prompt.compile(
company="Acme",
question="where is my order?",
# any variable you omit falls back to its default, or empty
)The rule for each declared variable, in order:
- Caller supplied a value → use it.
- No value, but a default is declared → use the default.
- No value, no default → render empty.
Compile never fails for a missing value. It fails for one thing: passing a variable the prompt doesn't declare — almost always a typo or a rename that didn't reach a call site.
Passing an undeclared variable raises a CompileError (both SDKs) before any provider call, listing every offending key at once. It's the one variable mistake worth catching — a bare {{compnay}} typo would otherwise render empty and silently send a wrong prompt.
Placeholders — inject a list of messages
A placeholder injects a list of messages at a point in the conversation — chat history, most often. It's a messages-typed variable, rendered in the stored template as:
{ "type": "placeholder", "name": "chat_history" }Pass an array of messages for it at compile time:
prompt.compile({
question: 'and the one before that?',
chat_history: [
{ role: 'user', content: [{ type: 'text', text: 'where is my order?' }] },
{ role: 'assistant', content: [{ type: 'text', text: 'It ships tomorrow.' }] },
],
});prompt.compile(
question="and the one before that?",
chat_history=[
{"role": "user", "content": [{"type": "text", "text": "where is my order?"}]},
{"role": "assistant", "content": [{"type": "text", "text": "It ships tomorrow."}]},
],
)Injected messages pass through untouched — their text is never re-rendered as a template, so history can safely contain {{braces}}.
Template syntax
Templates are a small, strict Mustache subset — the same engine on the backend and in both SDKs, so the playground renders a prompt exactly as your production app does.
| Syntax | Meaning |
|---|---|
{{name}} | Interpolate a value. |
{{a.b.c}} | Dot-path into an object value. |
{{#items}}…{{/items}} | Section — renders once if truthy, once per item if a list. |
{{^items}}…{{/items}} | Inverted section — renders when falsy or empty. |
{{.}} | The current item inside a list section. |
{{! note }} | Comment (stripped). |
Example — a list section building few-shot examples:
{{#examples}}
Q: {{question}}
A: {{answer}}
{{/examples}}A name used as a section head is declared as a json variable, so you pass it a list (or an object) and the section iterates:
prompt.compile({
examples: [
{ question: 'where is my order?', answer: 'It ships tomorrow.' },
{ question: 'can I return it?', answer: 'Yes, within 30 days.' },
],
});prompt.compile(examples=[
{"question": "where is my order?", "answer": "It ships tomorrow."},
{"question": "can I return it?", "answer": "Yes, within 30 days."},
])Partials, lambdas, and set-delimiters are not supported — they raise at save time rather than being silently ignored, so you never ship a template that half-works.
A missing value is never an error — it renders as its default, or empty. What is caught is passing a value for a variable the prompt doesn't declare, since that's almost always a typo. Because Trodo knows the declared set, it can catch that without guessing.
Values are inert
A variable's value is never treated as template source. If a value contains {{other}}, it renders as those literal characters and is never re-parsed:
prompt.compile({ company: '{{question}}', question: 'SECRET' });
// → "You are a support agent for {{question}}." (not "SECRET")Rendering is a single pass over a pre-parsed template, so injecting template syntax through variable data — including untrusted user input in a placeholder — is structurally impossible, not merely defended against.
Render a template without a prompt
If you just want the engine — say to reuse the exact same rendering somewhere else — call it directly. It's strict by default (an unknown variable raises); strict: false / strict=False renders unknowns as empty.
import { renderTemplate } from 'trodo-node';
renderTemplate('Hi {{name}}', { name: 'Ada' }); // 'Hi Ada'
renderTemplate('Hi {{name}}', {}); // throws — strict by default
renderTemplate('Hi {{name}}', {}, { strict: false }); // 'Hi 'from trodo import render_template
render_template("Hi {{name}}", name="Ada") # 'Hi Ada'
render_template("Hi {{name}}") # raises — strict by default
render_template("Hi {{name}}", strict=False) # 'Hi 'Access management
Who can view and edit a prompt: every teammate can view, the creator owns it, and the owner invites other team members as editors. How to grant and revoke edit access.
Configuration
A prompt version carries three things alongside its messages — model config, tools, and an output format — each edited as raw JSON in the dashboard and passed through unchanged by compile.