Templating

Raison prompts use Handlebars syntax for dynamic content. This guide covers every feature available in prompt templates.

Variables

Insert a variable value with double curly braces:

Hello, {{userName}}!
You are assisting with a {{context}} inquiry.

Call render with matching keys:

await raison.render("PROMPT_ID", {
  userName: "Alice",
  context: "billing",
});
// → "Hello, Alice!\nYou are assisting with a billing inquiry."

If a variable is not provided, it renders as an empty string.

Nested Objects

Access nested properties with dot notation:

User: {{user.name}}
Email: {{user.email}}
Account tier: {{user.account.tier}}
await raison.render("PROMPT_ID", {
  user: {
    name: "Alice",
    email: "alice@example.com",
    account: { tier: "pro" },
  },
});

Conditionals

Use {{#if}} to include content conditionally:

{{#if isPremium}}
You have access to all premium features.
{{else}}
Upgrade your plan to unlock premium features.
{{/if}}
await raison.render("PROMPT_ID", { isPremium: true });

Falsy values (false, null, undefined, 0, "", []) will take the {{else}} branch. An {{else}} block is optional.

Loops

Use {{#each}} to iterate over arrays:

Available tools:
{{#each tools}}
- {{this}}
{{/each}}
await raison.render("PROMPT_ID", {
  tools: ["search", "calculator", "calendar"],
});
// → "Available tools:\n- search\n- calculator\n- calendar"

Access the current index with @index (zero-based):

{{#each steps}}
Step {{@index}}: {{this}}
{{/each}}

For arrays of objects, access properties directly:

{{#each messages}}
{{role}}: {{content}}
{{/each}}
await raison.render("PROMPT_ID", {
  messages: [
    { role: "user", content: "Hello" },
    { role: "assistant", content: "Hi there!" },
  ],
});

Unescaped Output

By default, the Raison SDK compiles templates with noEscape: true — HTML special characters are never escaped. You do not need triple braces ({{{value}}}) for raw output. Standard {{value}} already outputs the value as-is.

Triple braces work but behave identically to double braces in the SDK.

Escaping Braces

To output a literal {{ in your prompt without it being treated as a variable, escape it with a backslash:

Use the syntax \{{variableName}} to insert a variable.

Renders as: Use the syntax {{variableName}} to insert a variable.

Custom Helpers

Helpers are functions you register once, then call from any prompt template. They extend Handlebars with your own logic.

Registering Helpers

Register helpers at application startup, before rendering any prompts:

import { Raison } from "raison";

// Register before creating Raison instances
Raison.registerHelper("uppercase", (str: string) => str.toUpperCase());
Raison.registerHelper("lowercase", (str: string) => str.toLowerCase());
Raison.registerHelper("json", (obj: unknown) => JSON.stringify(obj, null, 2));

Helpers are global — they apply to all Raison instances and all prompt renders in the process.

Calling Helpers in Templates

User: {{uppercase userName}}
Context: {{json metadata}}

Common Helper Examples

Date formatting:

Raison.registerHelper("formatDate", (date: string | Date) => {
  return new Date(date).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
  });
});
Today is {{formatDate currentDate}}.

JSON formatting (for embedding structured context):

Raison.registerHelper("json", (obj: unknown) => JSON.stringify(obj, null, 2));
User context:
{{json userContext}}

Conditional helper (inline ternary):

Raison.registerHelper("ifEqual", function (a, b, options) {
  return a === b ? options.fn(this) : options.inverse(this);
});
{{#ifEqual plan "enterprise"}}
You have enterprise-level access.
{{else}}
Contact sales to upgrade.
{{/ifEqual}}

Truncation:

Raison.registerHelper("truncate", (str: string, length: number) => {
  if (str.length <= length) return str;
  return str.slice(0, length) + "...";
});
Summary: {{truncate description 200}}

Template Compilation Errors

If a template is syntactically invalid (e.g., an unclosed block), render catches the error and returns the raw template content as a safe fallback. Your application continues working — it just receives the unrendered template text instead of a processed string.

{{#if condition

The above would fail to compile. render would return the raw string "{{#if condition" rather than throwing.

If you need to detect compilation failures, check the returned string against the raw prompt content retrieved via findOne.

Combining Features

Full example combining conditionals, loops, and helpers:

You are a {{role}} assistant for {{uppercase companyName}}.

{{#if userContext}}
User information:
{{json userContext}}
{{/if}}

{{#if conversationHistory.length}}
Previous conversation:
{{#each conversationHistory}}
{{role}}: {{content}}
{{/each}}
{{/if}}

Today is {{formatDate currentDate}}.
const prompt = await raison.render("PROMPT_ID", {
  role: "customer support",
  companyName: "acme corp",
  userContext: { plan: "pro", joinedDate: "2024-01-15" },
  conversationHistory: [
    { role: "user", content: "I need help with my invoice" },
  ],
  currentDate: new Date().toISOString(),
});