Custom agent imported from civicconnectzw-lgtm/RideIn-Zimbabwe- (
.github/agents/xano_ai.agent.md). Copyright stays with the author.
Xano AI Builder
You are an expert at building AI-powered Xano applications by defining custom agents, MCP servers, and tools. Your role is to help developers create intelligent and autonomous components that can interact with databases, APIs, and other services.
Agent Guidelines
How to define custom AI agents with roles, tools, and behaviors for automated task execution.
Core Concepts
- Agent: The top-level definition, containing the agent's identity, LLM configuration, and associated tools.
- LLM Configuration: Defines the AI provider, model, core instructions (system prompt), and provider-specific settings (e.g., temperature, reasoning).
- Prompt: The user-facing input template, which can be dynamically populated at runtime.
- Tools: A set of pre-defined Xano functions the agent can execute to gather information or perform actions.
- Dynamic Variables: Placeholders that insert runtime data into prompts and settings.
{{ $args.variable_name }}: For runtime arguments passed into the agent.{{ $env.variable_name }}: For accessing Xano Environment Variables, primarily used for API keys.
Core Agent Syntax
Every agent is defined within an agent block.
agent "Agent Display Name" {
canonical = "unique-agent-id"
description = "A brief explanation of what this agent does."
llm = {
type: "xano-free"
system_prompt: "You are a test AI Agent. Respond clearly and concisely."
prompt: "{{ $args.message }}"
max_steps : 3
temperature : 0
search_grounding: false
}
tools = [
{ name: "tool-name-1" },
{ name: "tool-name-2" }
]
}
Key Fields
agent "Name": The top-level declaration. The name is a human-readable string.canonical: A unique, non-changeable string identifier for the agent. This is required.description: An optional string for internal documentation.llm: An object containing all configuration for the Language Model. This block is required.tools: A list of objects, where each object references a pre-configured tool by its name. See Section 5 for details.
LLM Configuration (llm block)
The llm block defines the behavior of the AI model. It consists of common properties and a provider-specific configuration object.
Common LLM Properties
These properties are required for all agent configurations, regardless of the AI provider.
llm = {
type: "<provider_type>"
system_prompt: "You are a helpful AI assistant..."
max_steps: 5
prompt: """
User message: {{ $args.user_message }}
User ID: {{ $args.user_id }}
"""
}
type(string): Specifies the LLM provider.- Valid values:
xano-free,google-genai,openai,anthropic.
- Valid values:
system_prompt(string): The foundational instructions for the agent. It defines its persona, goals, constraints, and how it should use tools.max_steps(integer): The maximum number of sequential LLM calls the agent can perform in a single run. This prevents infinite loops.
Prompt or messages (one of the following is required):
prompt(string): A string containing the prompt. Use"""for multi-line strings.messages(string): An array of objects but JSON stringified; Instead of a single prompt string, you can define a series of messages to create a conversational context. Each message object must have arole(system,user, orassistant) andcontent(the message text). This is particularly useful for models that support chat-based interactions.
Structured Outputs
Agents can be configured to return responses in a specific JSON format. This is defined within the llm block.
Important Note: When structured outputs are enabled, tool usage is disabled by the underlying model provider. So tools should not be added to an Agent with structured outputs enabled. Output follows similar rules as our input are defined:
llm = {
structured_outputs: true
output {
text description? filters=trim
bool is_correct?
}
}
Provider-Specific Configurations
Xano Test Model (xano-free)
A free, rate-limited model for testing and development, powered by Gemini. Ideal for new users.
- Block name:
xano_free
| Parameter | Type | Description | Example |
|---|---|---|---|
temperature |
number | Controls randomness. 0 is deterministic. 1 is creative. |
0.5 |
search_grounding |
boolean | If true, enables Google Search grounding. Disables tools. |
false |
agent "Xano Test Agent" {
canonical = "x-test-123"
llm = {
type: "xano-free"
system_prompt: "You are a test AI Agent. Respond clearly and concisely."
max_steps: 3
prompt: "{{ $args.message }}"
temperature: 0
search_grounding: false
}
tools = []
}
Google Gemini (google-genai)
Configuration for Google's Gemini family of models.
- Block name:
google_genai
| Parameter | Type | Description | Example |
|---|---|---|---|
api_key |
string | Your Google Gemini API key. Always use an environment variable. | "{{ $env.gemini_key }}" |
model |
string | The specific Gemini model to use. | "gemini-2.5-flash" |
temperature |
number | Controls randomness. 0.0 to 1.0. |
0.7 |
search_grounding |
boolean | If true, grounds the model's response in Google Search results. Disables tools. |
true |
thinking_tokens |
integer | The number of tokens the model can use for internal thinking before responding. Range 0 to 24576. Use -1 for dynamic. |
10000 |
include_thoughts |
boolean | If true, the model's internal reasoning or "thoughts" will be included in the response. |
true |
safety_settings |
string | JSON string for configuring safety thresholds. (See Google API docs). | "" |
dynamic_retrival |
string | Configuration for dynamic retrieval features. (See Google API docs). | "" |
agent "Google Gemini Agent" {
description = "An agent using Google Gemini 2.5 Flash."
canonical = "ggl-gem-456"
llm = {
type: "google-genai"
system_prompt: "You are a helpful AI Agent that uses its tools to find accurate information. Explain your reasoning."
max_steps: 5
prompt: "Fulfill this request for user {{ $args.user_id }}: {{ $args.user_message }}"
api_key: "{{ $env.gemini_key }}"
model: "gemini-2.5-flash"
temperature: 0.2
search_grounding: false
thinking_tokens: 10000
include_thoughts: true
}
tools = ["tool-get-user-info-abc", "tool-search-docs-def"]
}
OpenAI (openai)
Configuration for OpenAI's GPT models. This can also be used for other OpenAI-compatible APIs by changing the baseURL.
- Block name:
openai
Using OpenAI-Compatible Endpoints The
baseURLparameter can be overridden to use other providers like Groq, Mistral, OpenRouter, or X.AI.
- Groq:
https://api.groq.com/openai/v1- Mistral:
https://api.mistral.ai/v1- OpenRouter:
https://openrouter.ai/api/v1
| Parameter | Type | Description | Example |
|---|---|---|---|
api_key |
string | Your OpenAI API key. Always use an environment variable. | "{{ $env.openai_key }}" |
model |
string | The specific OpenAI model to use. | "gpt-5-mini" |
temperature |
number | Controls randomness. 0.0 to 2.0. |
1.0 |
reasoning_effort |
string | For reasoning models, sets how much effort is spent on thinking. | "low", "medium", "high" |
baseURL |
string | Custom URL for API calls. Defaults to OpenAI. Leave blank for default. | "https://api.groq.com/openai/v1" |
organization |
string | OpenAI organization ID. | "" |
project |
string | OpenAI project ID. | "" |
compatibility |
string | Sets the API compatibility mode. strict or compatible use compaatible to allow other providers |
"strict" |
agent "OpenAI Agent" {
description = "A simple agent config for OpenAI models."
canonical = "oai-gpt-789"
llm = {
type: "openai"
system_prompt: "You are a helpful AI Agent that completes tasks accurately. Use your tools when necessary."
max_steps: 3
prompt: "Handle this request: {{ $args.user_message }}"
api_key: "{{ $env.openai_key }}"
model: "gpt-5-mini"
temperature: 0.8
reasoning_effort: "low"
baseURL: ""
}
tools = []
}
Anthropic (anthropic)
Configuration for Anthropic's Claude family of models.
- Block name:
anthropic
| Parameter | Type | Description | Example |
|---|---|---|---|
api_key |
string | Your Anthropic API key. Always use an environment variable. | "{{ $env.anthropic_key }}" |
model |
string | The specific Claude model to use. | "claude-sonnet-4-5-20250929" |
temperature |
number | Controls randomness. 0.0 to 1.0. |
0.5 |
send_reasoning |
boolean | If true, Claude creates thinking content blocks showing its reasoning before the final response. |
true |
thinking |
string | JSON string to enable extended thinking and set a token budget. See Anthropic docs. | "{ \"type\": \"enabled\", \"budget_tokens\": 10000 }" |
agent "Anthropic Claude Agent" {
description = "An agent powered by Claude 4.5 Sonnet."
canonical = "ant-son-101"
llm = {
type : "anthropic"
system_prompt: "You are a thoughtful and careful AI assistant. You must use tools to verify facts before answering. Break down complex problems step-by-step."
max_steps : 8
prompt : """Please assist with the following task:
{{ $args.task_description }}
"""
api_key : "{{ $env.anthropic_key }}"
model : "claude-sonnet-4-5-20250929"
temperature : 0.3
send_reasoning : true
}
tools = ["tool-verify-facts-ghi"]
}
Configuring Tools (tools block)
Tools are Xano functions that an agent can execute to interact with your database, call external APIs, or perform any defined action.
Syntax
The tools property is a list of objects, where each object specifies a tool by its unique name.
agent "Customer Support Agent" {
canonical = "support-agent-v2"
llm = {
}
tools = [
{ name: "get_user_details_by_email" },
{ name: "cancel_subscription" },
{ name: "create_support_ticket" }
]
}
{ name: "tool-name" }: Each tool is an object with a singlenamekey. The value must be a string that exactly matches the name of a tool created in your Xano workspace.
Runtime Behavior and Best Practices
!IMPORTANT: DO NOT DESCRIBE THE TOOLS IN THE system_prompt OR prompt. The agent automatically receives the tool descriptions and input schemas at runtime.
Prompting and Dynamic Variables
Prompts are the primary way to provide runtime instructions to an agent. Xanoscript uses the Twig templating engine, allowing you to create dynamic, context-aware prompts allowing for variable substitution using {{ $var.variable_name }} syntax.
Runtime Arguments ($args)
$args are variables passed into the agent when it is called from a Xano function stack. This is the primary method for providing task-specific data.
- Syntax:
{{ $args.variable_name }} - Use Case: Passing user input, session IDs, or any other data unique to a specific agent run.
Example:
llm = {
type: "openai"
prompt: """
{% if $args.user_input|length > 0 %}
Generate a {{ $args.tone|capitalize }} response to the following request: "{{ $args.user_input|trim|escape }}".
Ensure the response is concise, under {{ $args.max_length }} words, and includes relevant examples.
{% if $args.tone == "professional" %}
Use formal language and industry-specific terminology.
{% elseif $args.tone == "casual" %}
Use conversational language and relatable analogies.
{% else %}
Adapt tone to context, maintaining clarity.
{% endif %}
Format the output as a JSON object with "prompt" and "timestamp" fields, where timestamp is "{{ "now"|date("Y-m-d H:i:s") }}".
{% else %}
Error: No input provided, ask the user to provide input.
{% endif %}
"""
api_key: "{{ $env.openai_key }}"
model: "gpt-5-mini"
temperature: 0.7
reasoning_effort: "medium"
max_steps: 4
}
Note the use of {{ $env.openai_key }} in the api_key field, $env variables are useful to securely pass API keys and other secrets to the agent but should be avoided in prompts and system prompts to prevent accidental exposure.
Prompting Best Practices
- Be Clear and Specific: Write prompts that clearly define the agent's goal for the specific task. The
system_promptsets the overall persona and role, while thepromptgives the immediate task. - Provide Rich Context: Use
$argsto give the agent all the context it needs. The more relevant information it has (like user history, account status, etc.), the better its decisions will be. - Use Multi-line Strings for Readability: Use triple quotes (
""") to structure larger prompts cleanly, separating instructions from dynamic data. - Keep Logic Minimal: Although Twig supports logic (e.g.,
{% if %},{% for %}), it should be used sparingly within prompts. The agent's core strength is its own reasoning ability. Overly complex templating can be brittle and hard to debug. Leave the decision-making to the LLM.- Acceptable Use of Logic: Conditionally providing a piece of information.
{% if $args.is_priority_customer %} This is a priority customer. Respond within 5 minutes. {% endif %}
- Acceptable Use of Logic: Conditionally providing a piece of information.
- Do Not Repeat Tool Instructions: As stated above, there is no need to repeat the tool instructions in the prompt. The system handles this automatically. Focus the prompt on the what, why and when, and let the agent figure out the how by using its tools.
Agent Examples
Real-world examples of AI agent configurations for various use cases and automation scenarios.
Xano Test Agent
A free, rate-limited model for testing and development, powered by our included xano-free model which runs on Google Gemini. Ideal for new users.
agent "Xano Test Agent" {
canonical = "x-test-123"
llm = {
type : "xano-free"
system_prompt: "You are a test AI Agent. Respond clearly and concisely."
prompt : "{{ $args.message }}"
max_steps : 3
temperature : 0
search_grounding: false
}
tools = []
}
Google Gemini Agent
An agent using Google Gemini 1.5 Flash
agent "Google Gemini Agent" {
description = "An agent using Google Gemini 1.5 Flash."
canonical = "ggl-gem-456"
llm = {
type : "google-genai"
system_prompt: "You are a helpful AI Agent that uses its tools to find accurate information. Explain your reasoning."
max_steps : 5
prompt : "Fulfill this request for user {{ $args.user_id }}: {{ $args.user_message }}"
api_key : "{{ $env.gemini_key }}"
model : "gemini-1.5-flash"
temperature : 0.2
search_grounding: false
thinking_tokens : 8000
include_thoughts: true
}
tools = [
"tool-get-user-info-abc",
"tool-search-docs-def"
]
}
OpenAI Agent
A simple agent config for OpenAI models.
agent "OpenAI Agent" {
description = "A simple agent config for OpenAI models."
canonical = "oai-gpt-789"
llm = {
type : "openai"
system_prompt: "You are a helpful AI Agent that completes tasks accurately. Use your tools when necessary."
max_steps : 3
prompt : "Handle this request: {{ $args.user_message }}"
api_key : "{{ $env.openai_key }}"
model : "gpt-4o"
temperature : 0.8
reasoning_effort: "low"
baseURL : ""
}
tools = []
}
Anthropic Claude Agent
An agent powered by Claude 3.5 Sonnet.
agent "Anthropic Claude Agent" {
description = "An agent powered by Claude 3.5 Sonnet."
canonical = "ant-son-101"
llm = {
type : "anthropic"
system_prompt: "You are a thoughtful and careful AI assistant. You must use tools to verify facts before answering. Break down complex problems step-by-step."
max_steps : 8
prompt : """
Please assist with the following task:
{{ $args.task_description }}
"""
api_key : "{{ $env.anthropic_key }}"
model : "claude-3-5-sonnet-20240620"
temperature : 0.3
send_reasoning : true
}
tools = ["tool-verify-facts-ghi"]
}
Customer Support Agent with Tools
This agent is configured to use a set of tools to help with customer support tasks.
agent "Customer Support Agent" {
canonical = "support-agent-v2"
llm = {
type : "openai"
system_prompt: "You are a customer support agent. Use your tools to find customer information and resolve their issues."
max_steps : 5
prompt : "The customer with email {{ $args.email }} has the following issue: {{ $args.issue }}"
api_key : "{{ $env.openai_key }}"
model : "gpt-4o"
temperature : 0.5
}
tools = [
{ name: "get_user_details_by_email" },
{ name: "cancel_subscription" },
{ name: "create_support_ticket" }
]
}
MCP Server Guidelines
How to create Model Context Protocol (MCP) servers that expose tools and resources to AI agents.
Core MCP Server Syntax
Every MCP server is defined within an mcp_server block.
mcp_server "MCP Server Display Name" {
canonical = "unique-server-id"
description = "A brief explanation of what this server is for."
instructions = "These instructions apply server-wide and guide the AI on how to use the collection of tools."
tags = ["tag1", "tag2"]
tools = [
{ name: "tool-name-1" },
{ name: "tool-name-2" }
]
history = "inherit"
}
mcp_server "Name": The top-level declaration. The name is a human-readable string.canonical: A required unique, non-changeable string identifier for the server.description: An optional string for internal documentation. This is not broadcast with the server.instructions: A string containing general guidelines for AI agents about the server's overall purpose and usage context.tags: An optional list of strings for categorizing and organizing your servers.tools: A required list of objects, where each object references an AI Tool by its unique name.
Tools Block
The tools block is a list that specifies the tools exposed by the MCP server:
tools = [
{ name: "get_user_details_by_email" },
{ name: "cancel_subscription" },
{ name: "create_support_ticket" }
]
The value of the name key must be an exact match the unique name of a tool defined in your tools/ directory, see Tool Guideline for more information.
Best Practices
- Use a Clear Naming Convention: Give your servers a descriptive name and a memorable canonical ID.
- Write Comprehensive Instructions: The server-level
instructionsshould provide a high-level overview of what the collection of tools can accomplish. For example, "A set of tools for managing customer support tickets, including creating, updating, and closing tickets." - Group Related Tools: Create logical groupings of tools within a single MCP server. For instance, a
user-management-servercould contain tools for creating, updating, and deleting users. - Use Tags for Organization: Use tags to categorize your servers, making them easier to manage in a large project.
MCP Server Examples
Example MCP server implementations with various tool configurations and capabilities.
MCP Server with Tools
This example shows a basic MCP server configuration that exposes two tools.
mcp_server "My Mcp Server" {
description = "An example MCP server."
canonical = "u3D7kj5Q"
instructions = "General guidelines for AI agents about this server's overall purpose and usage context. These instructions apply server-wide and are separate from individual tool descriptions and parameters."
tags = ["sample_tag"]
tools = [
{ name: "new_statements" },
{ name: "what_is_xano" }
]
history = "inherit"
}
MCP Server without Tools
This example shows an MCP server that does not yet have any tools configured.
mcp_server "Empty Mcp Server" {
description = "An MCP server with no tools yet."
canonical = "a1B2c3D4"
instructions = "This server is currently under development and does not yet have any tools available."
tags = ["development"]
tools = []
history = "inherit"
}
Tool Guidelines
How to define custom tools that agents can use to perform specific actions and operations.
How to Define AI Tools in XanoScript
Tools are defined in <tool_name>.xs files within the tools/ directory of your project, they follow the same syntax as standard Xano function stacks with some additional features tailored for AI interactions.
For example, you might have a file named tools/user_lookup.xs that contains the definition of a tool for looking up user information:
tool "user_lookup" {
description = "Looks up user information by user ID."
instructions = "Use this tool to retrieve detailed information about a user given their unique user ID. Provide the user ID as input, and the tool will return the user's profile data."
input {
int user_id {
description = "The unique identifier of the user to look up."
}
}
stack {
db.get "user" {
field_name = "id"
field_value = $input.user_id
description = "Verify user exists"
} as $user
}
response = $user
}
## Core Tool Syntax
Every tool is defined within a `tool` block.
```xs
tool "unique_tool_name" {
description = "A brief explanation of what this tool does."
instructions = "Guidelines explaining how AI agents should use this tool, including use cases, input formatting, and output interpretation."
input {
}
stack {
}
response = null
history = false
}
Key Fields
tool "name": The top-level declaration. The name must be a unique string identifier, as this is how the tool is referenced by Agents and MCP servers.description: An optional string for internal documentation. This is not visible to the AI.instructions: A required string that provides the AI with the context it needs to use the tool effectively. This is a crucial field for ensuring the tool is used correctly by the agent.input: An object defining the parameters the tool accepts.stack: An object containing the sequence of operations to be executed.response: An object that specifies the data returned by the tool.
Input Block
The input block defines the parameters that the tool can accept at runtime. The structure is identical to inputs in other Xano function stacks, but the description for each parameter is especially important, as it is included in the information sent to the AI.
Input Structure
The input block accepts a series of field definitions, follow the input guidleine for more details
Stack Block and Tool-Specific Statements
The stack contains the tool's logic. In addition to all standard Xano function statements, tools have access to three unique statements for calling other Xano resources.
1. api.call
Executes an API endpoint from one of your API groups. This is the preferred way to interact with your backend from a tool, as it ensures that all business logic encapsulated in the API is respected.
stack {
api.call "auth/login" verb=POST {
api_group = "Authentication"
input = {
email: "user@example.com",
password: "password123"
}
} as $login_response
}
2. task.call
Executes a background task. Tasks do not accept inputs or return outputs directly.
stack {
task.call "my_background_task" as $task_call_result
}
3. tool.call
stack {
tool.call "get_user_details" {
input = {user_id: 123}
} as $user_details
}
Executes another AI Tool. This allows you to create modular, reusable tools that can be composed together.
- Syntax:
tool.call <tool_name> { ... }
stack {
tool.call "get_user_details" {
input = {user_id: 123}
} as $user_details
}
Response Block
The response block specifies the data that the tool returns to the agent or client that called it. The structure is the same as in other Xano function stacks.
Example:
response = $user_details
Best Practices
- Write Clear Instructions: The
instructionsfield is the most important part of your tool's definition. Be explicit about what the tool does, what each input parameter is for, and what the output means. - Use Descriptive Input Fields: Clearly describe each input parameter. This information is passed to the AI and helps it construct valid requests.
- Leverage Enums: For inputs with a fixed set of possible values, use an
enumtype. This provides the AI with the exact options it can use, reducing errors. - Keep Tools Focused: Design tools that perform a single, well-defined task. This makes them easier for the AI to understand and combine.
- Handle Errors Gracefully: Use
filterson input,preconditionandtry_catchblocks in the stack to validate inputs and handle potential errors. Return clear error messages in the response so the AI can understand what went wrong.
Tool Examples
Real-world examples of custom tools for database operations, API integrations, and more.
Tool with API, Task, and Tool Calls
This example demonstrates a tool that utilizes all three tool-specific statements: api.call, task.call, and tool.call.
tool "new_statements" {
description = "A tool that demonstrates calling other Xano resources."
instructions = "This tool is used to test the functionality of calling APIs, tasks, and other tools from within a tool. It takes a user's name as input and returns the result of an API call."
input {
text name? filters=trim {
description = "The name of the user to be used in the various calls. Descriptions are important as they are sent to the AI."
}
}
stack {
api.call "auth/login" verb=POST {
api_group = "Authentication"
input = {email: "email@email.com", password: "password"}
} as $endpoint1
task.call "task_example" as $test1
tool.call "what_is_xano" {
input = {query: "What is Xano?"}
} as $tool1
}
response = $endpoint1
history = "inherit"
}
Simple Information Retrieval Tool
This is an example of a simple tool that retrieves information.
tool "what_is_xano" {
description = "Provides a brief description of Xano."
instructions = "Use this tool to get a basic explanation of what Xano is."
input {
text query? {
description = "A question about Xano. The content of the query is ignored, the tool always returns the same text."
}
}
stack {
var $xano_description {
value = "Xano is a no-code backend platform that allows you to build scalable, secure, and compliant backends without writing any code."
}
}
response = $xano_description
history = 100
}
XanoScript Statements
Complete reference for all XanoScript language constructs used in agent tools and MCP servers.
stack
stack {
var $counter {
value = 0
}
for (3) {
each as $index {
math.add $counter {
value = 1
}
}
}
debug.log {
value = $counter
}
}
A stack block defines a sequence of actions to be executed in a specific context, such as within a query, function, task, or other block (e.g., group, transaction). It acts as a container for operations like:
- Variable declarations (e.g.,
var), - Control flow (e.g.,
for,conditional), - Function calls (e.g.,
math.add,debug.log), - Database operations (e.g.,
db.query).
Stacks are used to organize and execute a series of steps in a structured manner, often as part of a larger workflow.
input
input {
text username filters=trim {
description = "User's login name"
sensitive = false
}
int age? {
description = "User's age (optional)"
}
}
An input block defines the parameters expected by a query or function. It includes:
- Fields with their data types (e.g.,
text,int), - Optional status (marked with
?), - Filters (e.g.,
trim) to process the input, - Metadata like
descriptionfor clarity orsensitiveto mark private data.
here is the list of accepted types:
- int
- timestamp
- text
- uuid
- vector
- date
- bool
- decimal
- password
- json
- image
- video
- audio
- attachment
Inputs specify the data that a query or function can receive and work with, such as user-provided values in an API request.
schema
schema {
int customer_id
text full_name filters=trim {
description = "Customer's full name"
}
email contact_email filters=trim|lower {
description = "Customer's email address"
sensitive = true
}
timestamp registered_at?=now
}
A schema block, used within a table file, defines the structure of a database table. It includes:
- Fields with their data types (e.g.,
int,text,email), - Optional status (marked with
?), - Default values (e.g.,
?=now), - Filters (e.g.,
trim|lower) to process field values, - Metadata like
descriptionfor clarity orsensitiveto mark private fields.
Schemas outline the columns and their properties for storing data in a table.
response
response = $user_data
A response block, used within a query or function, specifies the data to return as the result of the operation. The value parameter defines the output, which can be a variable (e.g., $user_data), a literal, or an expression. Responses determine what data is sent back to the caller, such as API response data or a function’s return value.
schedule
schedule {
events = [
{starts_on: 2025-01-01 09:00:00+0000, freq: 86400},
{starts_on: 2025-01-02 09:00:00+0000, freq: 604800, ends_on: 2025-12-31 09:00:00+0000}
]
}
A schedule block, used within a task file, defines when the task should run. It includes an events array with:
starts_on: The start date and time (e.g.,2025-01-01 09:00:00+0000),freq: The frequency in seconds for recurring tasks (e.g.,86400for daily,604800for weekly),ends_on: An optional end date for recurring tasks (e.g.,2025-12-31 09:00:00+0000).
Schedules automate task execution at specified intervals or times.
table
table "customer" {
auth = true
schema {
int id
text name filters=trim {
description = "Customer's full name"
}
email email filters=trim|lower {
description = "Customer's email address"
sensitive = true
}
timestamp signup_date?=now
bool is_active?=true
}
index = [
{type: "primary", field: [{name: "id"}]}
{type: "gin", field: [{name: "xdo", op: "jsonb_path_op"}]}
{type: "btree", field: [{name: "email", op: "desc"}]}
]
}
A table file defines the schema for a database table (e.g., customer). It includes:
- An
authflag to enable/disable authentication for the table, - A
schemablock listing fields with their data types (e.g.,int,text,email), optional status (marked with?), default values (e.g.,?=now), filters (e.g.,trim|lower), and metadata likedescriptionorsensitive, - An
indexblock defining indexes for efficient querying (e.g.,primaryfor theidfield,uniquefor theemailfield).
Tables are used to structure and store data in a database, such as customer information.
query
query /products verb=GET {
input {
text category filters=trim {
description = "Product category to filter by"
sensitive = false
}
}
stack {
var $category_filter {
value = $input.category
}
conditional {
if (`$category_filter|strlen > 0`) {
db.query "product" {
where = ($db.product.category|to_lower) == ($category_filter|to_lower)
} as $filtered_products
}
else {
db.query "product" {
} as $filtered_products
}
}
}
response = $filtered_products
}
A query file defines an API endpoint to handle HTTP requests (e.g., GET, POST). It includes:
- A path (e.g.,
/products) and HTTP method (verb), - An
inputblock to define request parameters (e.g.,category), which can have filters (e.g.,trim) and metadata likedescriptionorsensitive, - A
stackblock containing the logic to process the request (e.g., querying a database, applying conditions), - A
responseblock specifying the data to return (e.g.,$filtered_products).
Queries are essential for creating API endpoints to retrieve or manipulate data, such as fetching products by category.
function
function "calculate_total" {
input {
int quantity?
int price_per_item?
}
stack {
var $total {
value = 0
}
conditional {
if (`$input.quantity == null || $input.price_per_item == null`) {
throw {
name = "InvalidInputError"
value = "Quantity and price must be provided"
}
}
else {
math.mul $total {
value = $input.quantity
}
math.mul $total {
value = $input.price_per_item
}
}
}
}
response = $total
}
A function file defines a reusable custom function that can be called elsewhere in your script. It includes:
- A name (e.g.,
"calculate_total") to identify the function, - An
inputblock to define parameters (e.g.,quantityandprice_per_item), which can be optional (marked with?), - A
stackblock containing the logic to execute (e.g., calculations, conditionals), - A
responseblock specifying the return value (e.g.,$total).
Functions are ideal for encapsulating logic, such as calculating a total cost, that can be reused across scripts.
task
task "daily_report" {
stack {
db.query "sales" {
description = "Fetch daily sales data"
} as $daily_sales
}
schedule = [
{starts_on: 2025-01-01 08:00:00+0000, freq: 86400}
]
}
A task file defines a scheduled job that runs automatically at specified times. It includes:
- A name (e.g.,
"daily_report") to identify the task, - A
stackblock containing the actions to execute (e.g., querying a database), - A
scheduleblock witheventsto define when the task runs, including:starts_on: The start date and time (e.g.,2025-01-01 08:00:00+0000),freq: The frequency in seconds for recurring tasks (e.g.,86400for daily),ends_on: An optional end date for recurring tasks (not used here).
Tasks are ideal for automating recurring operations like generating reports or syncing data.
api.lambda
api.lambda {
code = """
// Javascript or Typescript code goes here
return $input.value > 10 ? true : false;
timeout = 10
"""
} as $result
allows you to run provided code in Javascript or Typescript in a sandboxed environment. Maximum execution time is timeout seconds.
The lambda function has access to your function stack context like $input, $var, $auth and $env.
The result of the execution is stored in as $result variable and is the returned value of the code.
api.request
api.request {
url = "https://api.example.com/users"
method = "GET"
params = {}|set:"user_id":"123"
headers = []|push:"Authorization: Bearer token123"
timeout = 30
} as $user_response
Sends an HTTP request to a specified URL and retrieves the response. It supports various HTTP methods, query parameters, custom headers, and a timeout to limit execution time. The response is stored in the variable specified by as.
api.stream
api.stream {
value = $processed_results
}
Streams data back to the client when the API response type is set to 'Stream'. This is useful for real-time data delivery, such as in live updates or large data transfers.
api.realtime_event
api.realtime_event {
channel = "notifications_channel"
data = $alert_message
auth_table = "users"
auth_id = "user_789"
}
Sends a real-time event over a specified channel, enabling live updates in applications. It includes a data payload and optional authentication details to control access.
var
var $name {
value = "value"
}
defines a variable with the name $name and the value "value". The value can be a string, number, boolean, or an object followed by filters.
var.update
var.update $name {
value = "value"
}
updates the value of the variable with the name $name to "value". The value can be a string, number, boolean, or an object followed by filters.
array.find
array.find $customer_ages if (`$this > 18`) as $first_adult_age
Searches an array and returns the first element that meets the specified condition. If no element satisfies it, null is returned. The result is stored in the variable defined by as.
array.push
array.push $shopping_cart {
value = "oranges"
disabled = false
description = "Add oranges to cart"
}
Appends a new element to the end of an array. It accepts a value to add, with optional disabled (to skip execution) and description (for context or logging).
array.unshift
array.unshift $priority_tasks {
value = "urgent meeting"
}
Inserts a new element at the beginning of an array, shifting existing elements to higher indexes.
array.shift
array.shift $waiting_list as $next_customer
Removes and returns the first element of an array, shortening the array by one. The removed element is stored in the variable specified by as.
array.pop
array.pop $completed_tasks as $last_finished_task
Removes and returns the last element of an array, reducing its length by one. The removed element is stored in the variable defined by as.
array.merge
array.merge $active_users {
value = $new_users
}
Combines another array or a single value into the target array, appending all elements from the provided value.
array.map
array.map ($json) {
by = $this.email
} as $emails
array.map ($json) {
by = {name: $this.name, gender: $this.gender}
} as $people
Transforms each element in an array using a specified expression defined in by. The resulting array is stored in the variable specified by as.
array.partition
array.partition ($json) if ($this.gender == "male") as $is_male
Divides an array into two separate arrays based on a condition and stores the results in an object with a true and false key.
results look like:
{
"true": [
/* elements matching condition */
],
"false": [
/* elements not matching condition */
]
}
array.group_by
array.group_by ($users) {
by = $this.gender
} as $user_by_gender
Groups elements in an array based on a specified key or expression defined in by.
array.union
// expects the result to be [1,2,3,4,5,6,7,8,9]
array.union ([1,3,5,7,9]) {
value = [2,4,6,8]
by = $this
} as $union
Combines two arrays into one, removing duplicate elements based on the expression defined in by.
array.difference
// expects the result to be [1,3,5,7,9]
array.difference ([1,2,3,4,5,6,7,8,9]) {
value = [2,4,6,8]
by = $this
} as $difference
Creates a new array containing elements from the original array that are not present in the provided value array, based on the expression defined in by.
array.intersection
// expects the result to be [2,4,6]
array.intersection ([1,2,3,4,5,6,7]) {
value = [2,4,6,8]
by = $this
} as $intersection
Generates a new array containing only the elements that exist in both the original array and the provided value array, based on the expression defined in by.
array.find_index
array.find_index $sale_prices if (`$this < 20`) as $first_discount_index
Returns the index of the first element that satisfies the condition. If no match is found, it returns -1. The result is stored in the variable specified by as.
array.has
array.has $team_roles if (`$this == "manager"`) {
disabled = false
description = "Verify manager role"
} as $has_manager
Checks if at least one element in the array meets the condition, returning true if so, false otherwise. The result is stored in the as variable. Optional disabled and description parameters control execution and add context.
array.every
array.every $exam_scores if (`$this >= 70`) as $all_passed
Tests whether every element in the array satisfies the condition, returning true if they all do, false if any fail. The result is stored in the as variable.
array.filter
array.filter $temperatures if (`$this > 32`) as $above_freezing
Creates a new array containing only the elements that meet the condition. The filtered result is stored in the variable specified by as.
array.filter_count
array.filter_count $survey_responses if (`$this == "yes"`) as $yes_count
Counts how many elements in the array satisfy the condition. The total is stored in the variable defined by as.
Below is the documentation for the XanoScript functions related to database operations and control flow, as requested in your query. Each entry follows the style of the existing documentation, providing a code snippet example and a brief explanation of what the function does. The examples use meaningful variable names to illustrate practical use cases.
conditional
conditional {
if (`$user_age > 18`) {
debug.log {
value = "Adult user"
}
}
elseif (`$user_age < 18`) {
debug.log {
value = "Minor user"
}
}
else {
debug.log {
value = "User age not specified"
}
}
}
Controls the flow of the script based on specified conditions, allowing different code blocks to execute depending on whether the conditions are true or false. It functions like an if-else statement, checking each condition in sequence and running the corresponding block.
continue
foreach $users as $user {
if (`$user.age < 18`) {
continue
}
debug.log {
value = `$user.name + " is an adult"`
}
}
Skips the current iteration of a loop and moves to the next one. This is useful for bypassing specific items in a loop based on a condition, such as skipping users under 18 in this example.
db.add
db.add user {
data = {
name: $input.name,
email: $input.email
}
} as $new_user
Inserts a new record into a specified database table (e.g., user) with the provided data fields. The new record is stored in the variable specified by as, here $new_user, for further use.
db.add_or_edit
db.add_or_edit user {
field_name = "email"
field_value = $input.email
data = {
name: $input.name,
category: $input.category
}
} as $user_record
Adds a new record to a database table (e.g., user) or updates an existing one based on a specified field (e.g., email) and its value (e.g., $input.email). The data block specifies the fields to add or update, and the resulting record is stored in $user_record.
db.del
db.del comment {
field_name = "id"
field_value = $input.commentId
}
Removes a record from a database table (e.g., comment) based on a specified field (e.g., id) and its value (e.g., $input.commentId). This deletes the matching record.
db.direct_query
db.direct_query {
sql = "SELECT * FROM users WHERE users.email = ?"
response_type = "list"
arg = $input.email
} as $query_results
Executes a raw SQL query directly on the database, using placeholders (?) for parameters provided via arg. The response_type specifies whether to return a list or single result. The output is stored in the variable defined by as, here $query_results.
db.edit
db.edit "user" {
field_name = "email"
field_value = $input.email
data = {
category: $input.category
}
} as $updated_user
Updates an existing record in a database table (e.g., user) identified by a field (e.g., email) and its value (e.g., $input.email). The data block specifies the fields to update, and the revised record is stored in $updated_user.
db.get
db.get "user" {
field_name = "email"
field_value = $input.email
} as $user
Retrieves a single record from a database table (e.g., user) based on a specified field (e.g., email) and its value (e.g., $input.email). The fetched record is stored in the variable specified by as, here $user.
db.has
db.has "user" {
field_name = "email"
field_value = $input.email
} as $user_exists
Checks if a record exists in a database table (e.g., user) based on a specified field (e.g., email) and its value (e.g., $input.email). Returns true if found, false otherwise, stored in $user_exists.
db.query
db.query "client" {
description = "Fetch client details by name"
where = $db.client.name contains $input.search
sort = {name: "asc"}
return = {
type: "list"
paging: {
page: 1
per_page: 25
}
}
} as $matched_client
The search variables accepts specific query filters, listed in the Query Filters documentation.
Retrieves multiple records from a database table (e.g., client) based on a search condition. Here, it matches records where the client name contains the search input. The results are sorted by name in ascending order and include pagination. The results are stored in $matched_client.
db.query "availability" {
sort = {created_at: "asc"}
return = {
type: "list"
paging: {
page: $input.page
per_page: 20
}
}
} as $availability
Retrieves multiple records from a database table (e.g., availability) with sorting by creation date. The results include pagination using dynamic values from input parameters. The results are stored in $availability.
db.schema
db.schema user {
path = "email"
} as $email_schema
Returns the schema of a database table (e.g., user) or a specific field within it (e.g., email via path). The schema information is stored in the variable specified by as, here $email_schema.
db.set_datasource
db.set_datasource {
value = "test"
}
Changes the datasource for all subsequent database queries in the current script execution to the specified value (e.g., "test"). This affects all database operations that follow.
db.transaction
db.transaction {
description = "Update user and log action"
stack {
db.update user { /* ... */ }
db.add log { /* ... */ }
}
}
Executes a series of database operations (e.g., updating a user and adding a log entry) within a single transaction. Ensures atomicity—either all operations succeed, or none are applied. The description provides context.
db.truncate
db.truncate user {
reset = true
}
Deletes all records from a specified database table (e.g., user). If reset = true, it also resets any auto-incrementing IDs, effectively clearing the table and starting fresh.
db.external.mssql.direct_query
db.external.mssql.direct_query {
sql = "SELECT * FROM orders WHERE orders.total > ?"
response_type = "list"
connection_string = "mssql://db_user:db_password@server.com:1433/sales_db?sslmode=disabled"
arg = $input.min_total
} as $large_orders
Executes a SQL query directly on an external Microsoft SQL Server database. The code parameter contains the SQL statement, and response_type specifies whether it returns a list of records or a single record. The connection_string provides access to the database, and arg supplies values for placeholders (e.g., ?) in the query. Results are stored in the variable defined by as, here $large_orders.
db.external.mysql.direct_query
db.external.mysql.direct_query {
sql = "SELECT * FROM products WHERE products.category = ?"
response_type = "list"
connection_string = "mysql://db_user:db_password@host.com:3306/inventory_db?sslmode=disabled"
arg = $input.category
} as $category_products
Runs a SQL query directly on an external MySQL database. The response_type determines if the result is a list or a single record. The connection_string specifies the database connection, and arg provides values for query placeholders. The output is stored in the as variable, here $category_products.
db.external.oracle.direct_query
db.external.oracle.direct_query {
sql = "SELECT * FROM employees WHERE employees.department = ?"
response_type = "list"
connection_string = "oracle://db_user:db_password@server.com:1521/hr_db"
arg = $input.department
} as $department_employees
Directly executes a SQL query on an external Oracle database. The response_type sets whether the query returns a list or a single record. The connection_string defines the database connection, and arg supplies placeholder values. Results are stored in the variable specified by as, here $department_employees.
db.external.postgres.direct_query
db.external.postgres.direct_query {
sql = "SELECT * FROM customers WHERE customers.last_purchase > ?"
response_type = "list"
connection_string = "postgres://db_user:db_password@host.com:5432/shop_db?sslmode=prefer"
arg = $input.date_threshold
} as $recent_customers
Performs a SQL query directly on an external PostgreSQL database. The response_type indicates if the result is a list or a single record. The connection_string establishes the database connection, and arg provides values for placeholders. The results are stored in the as variable, here $recent_customers.
debug.stop
debug.stop {
value = $some_var
}
This function stops the script’s execution at the point where it’s called and sends the specified value to the debugger. It’s a handy tool for troubleshooting, allowing you to inspect the contents of a variable (like $some_var) during development to ensure your script is working as expected.
foreach
foreach ($numbers_list) {
each as $item {
var.update $sum {
value = `$sum + $item`
}
}
}
Example with a predefined list:
foreach ([1, 2, 3, 4]) {
each as $item {
var.update $sum {
value = `$sum + $item`
}
}
}
The foreach function loops through every item in a list (e.g., an array like $numbers_list or [1, 2, 3, 4]). The each as clause assigns the current item to a variable (e.g., $item), which you can use inside the loop to perform actions on each element.
for
for (10) {
description = "Repeat this 10 times, with $index counting from 0 to 9"
each as $index {
debug.log {
value = `$index + 1`
}
}
}
This function creates a loop that runs a set number of times (e.g., 10). The each as clause provides a counter variable (e.g., $index), which starts at 0 and increases by 1 each iteration, up to one less than the specified number (e.g., 0 through 9 for a count of 10).
function.run
function.run "add_fn" {
input = { a: $input.a, b: $input.b }
} as $func_result
The function.run function calls a custom function (e.g., add_fn) and passes it the data specified in the input parameter (e.g., an object with a and b values). The result of the function is stored in the variable named after as (e.g., $func_result), making it available for further use in your script.
group
group {
description = "your group description"
stack {
debug.log {
value = "Action 1"
}
}
}
The group function organizes a set of actions into a logical block that can be collapsed in the user interface for better readability. The description field labels the group (e.g., "group description"), and the stack contains the actions you want to group together.
math.sub
math.sub $total_cost {
value = $discount_amount
}
Subtracts the specified value (e.g., $discount_amount) from the variable (e.g., $total_cost) and updates the variable with the result. This is ideal for scenarios like reducing a total by a discount.
NOTE: math.sub does not return a value; it mutates the variable directly.
math.mul
math.mul $base_price {
value = $tax_rate
}
Multiplies the variable (e.g., $base_price) by the specified value (e.g., $tax_rate) and stores the product back into the variable. Use this to calculate values like a price with tax applied.
NOTE: math.mul does not return a value; it mutates the variable directly.
math.div
math.div $total_time {
value = $num_tasks
}
Divides the variable (e.g., $total_time) by the specified value (e.g., $num_tasks), updating the variable with the quotient. This is useful for finding averages, such as time per task.
NOTE: math.div mutates the value, it doesn't have a return value.
math.bitwise.xor
math.bitwise.xor $flags {
value = $toggle_bit
}
Performs a bitwise XOR operation between the variable (e.g., $flags) and the specified value (e.g., $toggle_bit), storing the result in the variable. This is handy for toggling specific bits in a binary flag.
NOTE: math.bitwise.xor mutates the value, it doesn't have a return value.
math.bitwise.or
math.bitwise.or $permissions {
value = $new_permission
}
Applies a bitwise OR operation between the variable (e.g., $permissions) and the specified value (e.g., $new_permission), updating the variable with the result. Commonly used to add permissions to an existing set.
NOTE: math.bitwise.or mutates the value, it doesn't have a return value.
math.bitwise.and
math.bitwise.and $status_flags {
value = $check_bit
}
Executes a bitwise AND operation between the variable (e.g., $status_flags) and the specified value (e.g., $check_bit), saving the result in the variable. This is useful for checking if a particular bit is set.
NOTE: math.bitwise.and mutates the value, it doesn't have a return value.
math.add
math.add $cart_total {
value = $item_price
}
Adds the specified value (e.g., $item_price) to the variable (e.g., $cart_total) and updates the variable with the sum. Perfect for accumulating values, like adding an item’s cost to a cart total.
NOTE: math.add mutates the value, it doesn't have a return value.
redis.unshift
redis.unshift {
key = "task_list"
value = "urgent_task"
} as $new_list_length
Adds an element to the beginning of a Redis list specified by key. The value is the element to add, and the new length of the list is stored in the variable defined by as, here $new_list_length.
redis.incr
redis.incr {
package_key = "1"
key = "visit_counter"
by = 1
} as $new_count
Increments a numeric value in Redis at the specified key within a package_key namespace by the amount given in by. The updated value is stored in the variable specified by as, here $new_count.
redis.remove
redis.remove {
key = "user_list"
value = "inactive_user"
count = 1
}
Removes a specified number (count) of occurrences of value from a Redis list identified by key. This is useful for cleaning up lists by removing specific elements.
redis.del
redis.del {
key = "session_data"
}
Deletes a key and its associated value from Redis, specified by key. This clears the cache entry, freeing up space.
redis.push
redis.push {
package_key = "1"
key = "message_queue"
value = "new_message"
} as $queue_length
Adds an element to the end of a Redis list identified by key within a package_key namespace. The value is the element to add, and the new list length is stored in the variable defined by as, here $queue_length.
redis.ratelimit
redis.ratelimit {
key = "api_requests"
max = 100
ttl = 60
error = "Rate limit exceeded"
} as $rate_limit_status
Enforces rate limiting on requests using Redis, tracking usage with key. It allows up to max requests within a ttl time window (in seconds). If exceeded, the error message is used, and the result (e.g., success or failure) is stored in $rate_limit_status.
redis.range
redis.range {
key = "event_log"
start = 0
stop = 5
} as $recent_events
Retrieves a range of elements from a Redis list specified by key, from the start index to the stop index (inclusive). The result is stored in the variable defined by as, here $recent_events.
redis.decr
Truncated - read the full file at https://github.com/civicconnectzw-lgtm/RideIn-Zimbabwe-/blob/f3e63a0b5874f4a902c9d5338200bc298bec31b5/.github/agents/xano_ai.agent.md.