Variables & templating
Declared variables with optional defaults, message-list placeholders, and the strict Mustache-subset syntax that fills them. A missing value falls back to its default, or renders empty, it never fails.
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. |
There is no "required" flag. A variable already has a resolution rule, caller's value, else default, else empty, and that rule never needs to fail. Set the default to whatever an omitted call should produce.
Detection and the stored declarations are reconciled on save, server-side, so the declared list always matches what the messages actually reference.
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.
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. Names used inside the body (question, answer above) are per-item fields: each item of the list supplies them, and you never pass them to compile() yourself. They still appear on the Variables tab, marked as item fields, because a Default set there acts as the fallback whenever an item omits that field:
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.
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.