Skip to main content
Variables are the bridge between your application data and your prompts. This guide explains how the variable system works.

The Variable System

How Variables Work

1. Define Variables in Messages

In the web app, insert variables using the /variable command:
System: You are a customer support agent for {{company_name}}.

User: Customer {{customer_name}} asks:
{{query}}

Relevant context:
{{search_results}}
Each {{variable_name}} links to a Property that defines its type.

2. Variables Sync to Schema

When you add variables, the prompt’s input schema updates automatically:
{
  "type": "object",
  "properties": {
    "company_name": {"type": "string"},
    "customer_name": {"type": "string"},
    "query": {"type": "string"},
    "search_results": {"type": "string"}
  },
  "required": ["query"]
}
This happens in real-time as you edit—no manual schema management.

3. Provide Data at Runtime

At runtime, provide values that match the schema:
session = await client.create_prompt_session(
    prompt_id="...",
    session_data=ProductHelpInput(
        company_name="Acme Corp",
        customer_name="Alice",
        query="How do I reset my password?",
        search_results="[relevant docs as JSON]"
    )
)

4. Variables Get Substituted

When converting to provider format, variables are replaced:
# Original message block
"Customer {{customer_name}} asks: {{query}}"

# After substitution
"Customer Alice asks: How do I reset my password?"

Properties

A Property defines the type and metadata for a variable.

Property Types

TypeJSON SchemaPythonUse Case
String{"type": "string"}strText, IDs
Number{"type": "number"}floatScores, prices
Integer{"type": "integer"}intCounts
Boolean{"type": "boolean"}boolFlags
Object{"type": "object", ...}Nested modelStructured data
Array{"type": "array", ...}list[T]Collections

Special Formats

String properties can have formats:
{
  "type": "string",
  "format": "date"        // "2024-01-15"
}
{
  "type": "string",
  "format": "date-time"   // "2024-01-15T10:30:00Z"
}
{
  "type": "string",
  "format": "email"       // "user@example.com"
}
{
  "type": "string",
  "format": "uri"         // "https://example.com"
}

Complex Properties

Properties can define nested structures:
{
  "type": "object",
  "properties": {
    "id": {"type": "string"},
    "title": {"type": "string"},
    "content": {"type": "string"},
    "score": {"type": "number"}
  },
  "required": ["id", "title", "content"]
}
Or arrays of complex objects:
{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "title": {"type": "string"},
      "content": {"type": "string"}
    }
  }
}

The render() Transformation

Why render()?

Variables must be strings for prompt injection, but your code uses typed data. The render() method bridges this gap:
class ProductHelpInput(RenderableModel):
    customer_name: str           # Simple: just use as-is
    query: str
    documents: list[Document]    # Complex: needs serialization

    def render(self, **kwargs) -> dict[str, str]:
        return {
            "customer_name": self.customer_name,
            "query": self.query,
            "documents": json.dumps([d.model_dump() for d in self.documents])
        }

render() Input vs Output

# Input (typed)
input = ProductHelpInput(
    customer_name="Alice",
    query="How do I...",
    documents=[
        Document(title="FAQ", content="..."),
        Document(title="Guide", content="...")
    ]
)

# Output (all strings)
output = input.render()
# {
#   "customer_name": "Alice",
#   "query": "How do I...",
#   "documents": '[{"title": "FAQ", ...}, {"title": "Guide", ...}]'
# }

Customizing Format

Override render() to control formatting:
def render(self, **kwargs) -> dict[str, str]:
    # Markdown format
    docs_md = "\n".join([
        f"## {d.title}\n{d.content}"
        for d in self.documents
    ])

    return {
        "customer_name": self.customer_name,
        "query": self.query,
        "documents": docs_md
    }
Or XML:
def render(self, **kwargs) -> dict[str, str]:
    docs_xml = "<documents>\n" + "\n".join([
        f'  <doc title="{d.title}">{d.content}</doc>'
        for d in self.documents
    ]) + "\n</documents>"

    return {
        "customer_name": self.customer_name,
        "query": self.query,
        "documents": docs_xml
    }

Schemas

Input Schemas

Every prompt has an input schema that defines required variables:
prompt = await client.get_prompt("...", branch_name="main")

# Access the input schema
schema = prompt.input_schema
print(schema.name)         # "ProductHelpInput"
print(schema.exportedJSON) # Full JSON Schema
Input schemas are auto-generated from message variables. You don’t create them manually.

Tool Schemas

Tool schemas define function calling or structured output:
# Access tool schemas
for tool in prompt.tools:
    if tool.tool_type == "tool":
        print(f"Function: {tool.name}")
        print(f"Schema: {tool.schema}")
    elif tool.tool_type == "structured_output":
        print(f"Output schema: {tool.name}")
Tool schemas are created manually in the web app.

Variable Substitution

How It Works

Variable Formats

Variables can be inline or block:
FormatExampleDescription
InlineThe customer {{customer_name}} said...Variable embedded in text
Block{{search_results}}Variable is entire block
This affects rendering—block variables typically appear on their own line.

Schema Metadata

Schemas include Moxn metadata:
{
  "type": "object",
  "properties": {...},
  "x-moxn-metadata": {
    "schema_id": "uuid",
    "prompt_id": "uuid",
    "task_id": "uuid",
    "commit_id": "abc123"  // If from a commit
  }
}
Codegen’d models expose this:
class ProductHelpInput(RenderableModel):
    @classmethod
    @property
    def moxn_schema_metadata(cls) -> MoxnSchemaMetadata:
        return MoxnSchemaMetadata(
            schema_id="...",
            prompt_id="...",
            task_id="..."
        )

Best Practices

customer_query is better than q. Variable names appear in code and logs.
Use specific types (date, integer) rather than just string when possible.
Format complex data (markdown, XML) for better LLM comprehension.
Each prompt should have just the variables it needs.
When the number of items varies, use array types.

Next Steps