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:

PropertyMeaning
NameAuto-detected from the messages. Rename the {{token}} in the message and the declaration follows — it's not edited here.
DefaultUsed 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:

  1. Caller supplied a value → use it.
  2. No value, but a default is declared → use the default.
  3. 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.

SyntaxMeaning
{{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 '

On this page