Skip to main content
The goal of this page is to give you a quick walkthrough of how XanoScript works. By the end, you’ll have a good understanding of how XanoScript is written and executed, how to work with data, and how to use functions and filters. First, we’ll cover primitives (basic building blocks), inputs, and logic stacks. After that, we’ll cover variables and data manipulation, including dot notation, filters, and loops. Finally, we’ll cover conditionals and provide some basic examples. In each section, we’ll provide a build along example to help you understand how to use the concepts.

Primitives

A primitive is where everything starts. It represents a basic building block of anything you can build in Xano, such as APIs, AI Agents, or database tables. For each primitive, you will start by declaring the type of primitive and its name. An optional description can follow.
Basic primitive declaration format
// optional comment
<primitive_keyword> <name> <attribute_name>=<attribute_value> {
  <parameter_name> = <parameter_value>
  ...
}
For primitives that can have authentication enabled, you’ll specify this in the parent object:
API with authentication
// Returns a list of all users with formatted names and creation dates. 
query user_list verb=GET {
  auth = "user"
  ...
}
Each primitive type can have a different set of attributes required. For more information, see the dedicated documentation for each primitive.

Inputs

Inputs are the data that the logic might need to perform its operation. They are declared in the input block of the primitives that support them. Inputs are optional, but the block must be declared even if empty.
Input declaration format
input {
  <type> <name>
}
Some common input types are text, email, password, int, decimal, and enum. For a complete reference, see Data Types. You can make inputs optional, required, lists, or apply filters to transform them before use. Here are the available options:
OptionDescription
<input_name>?Makes the input optional
Example: text name?
<input_type>?Makes the input nullable
Example: text? name
requiredMakes the input required by not specifying optional
Example: text name required
<input_type>[]Makes the input a list
Example: text[] names
filters=<name>:<option>Applies filters to the input
Example: text name filters=trim|lower

Build Along: Defining the Primitive

Let’s declare an API called user_list. This is a GET API that will return a list of all users with formatted names and creation dates, and will use ‘user’ authentication. There will not be any inputs, but we still need to declare the input block.
API declaration example
// // Returns a list of all users with formatted names and creation dates. 
query user_list verb=GET {
  auth = "user"
  input {
  }
}

Stacks

Stacks are the core building blocks of writing logic in XanoScript. Inside of a stack, you’ll declare functions and their parameters. Functions begin with a namespace — basically, a category that the function is a part of. Each namespace is separated with a period. After the namespace, you’ll declare the function name and any parameters the function requires. Functions can also have a description, which is optional.
Stack with function declaration
stack {
  // Get all records from the user table.
  db.query user {
    return = {type: "list"}
  } as $users
}
If a parameter is not specified, the default value or no value will be used, depending on the behavior of the function. If a function outputs to a variable, that is defined after the object is closed. This is done by using the as keyword followed by the variable name.
This is a simple example of a function in a stack.
db.query user {
  description = "Get all records from the user table."
  return = {type: "list"}
} as $users

Build Along: Defining the Stack

Let’s declare a stack that will query the user table and return a list of all users. Later, we’ll expand on this stack to manipulate the data, but for now, we’ll just return the list of users to a variable called $users.
Stack declaration example
stack {
  db.query user {
    description = "Get all records from the user table."
    return = {type: "list"}
  } as $users
}

Responses

Responses are the output of a primitive. They are declared in the response block of the primitive.
Response declaration format
response = {...}

Build Along: Defining the Response

Let’s declare a response that will return the list of users.
Response declaration example
response = $users

Settings

Settings are the configuration of a primitive. Sometimes, a primitive will have a settings block, and sometimes the settings are integrated into the most parent object. If a setting is not specified, the default value or no value will be used, depending on the behavior of the primitive.
An example of settings defined after the response.
response = $formatted_users

// keep profile data up to a maximum of 100 statements for the execution of this query
history = 100

Build Along: A simple API endpoint

Here’s the complete API endpoint we’ve built so far. It’s a simple GET API that returns a list of all users from the user table. We’ll continue to iterate on this endpoint throughout the rest of the guide by adding the logic to transform the data with filters and a loop.
An example of an API endpoint that returns a list of all users from the user table.
// Query all user records
query all_users verb=GET {
  input {
  }

  stack {
    db.query user {
      return = {type: "list"}
    } as $model
  }

  response = $model
  
  // keep profile data up to a maximum of 100 statements for the execution of this query
  history = 100
}

Variables and Data Manipulation

Variables

Set and update variables using the var keyword.
Variable declaration and update
// Initialize an array to hold the formatted user data.
var $formatted_users {
    value = []
}

Build Along: Initializing a variable

Let’s initialize a variable called $formatted_users that will hold the formatted user data.
Variable declaration example
var $formatted_users {
  description = "Initialize an array to hold the formatted user data."
  value = []
}

For the following sections on Dot Notation and Filters, we’ll be building a little out of order; don’t worry, it’ll make sense once we start building our loop.

Dot Notation

Dot Notation is used to navigate inside of objects and arrays to target specific pieces of data. In the following object, {"a":"hello", "b":"world"}, we can target the value of the a key using the dot notation. The object is, for the sake of this example, contained in a variable called $x1.
Dot notation for object access
$x1.a
The dot notation can also be used to target specific items in arrays the same way. Assuming the array is ["apple", "banana", "cherry"] inside of a variable called $x1, we can target the value of the second item using the dot notation.
Dot notation for array access
$x1.1

Build Along: Using dot notation

In the API we’re building, we’ll be using dot notation to target specific values in each user object and manipulate them.
Dot notation example -- Targeting the created_at value of a user
  $user_item.created_at

Filters

Filters are used to manipulate data throughout your stack. They can be applied almost anywhere that data is generated or referenced; anything from creating a variable to manipulating the response object on the fly. They are applied using the | pipe character, and any filter options are separated by a colon immediately after.
Filter syntax format
<data>|<filter_name>:<filter_option_1>:<filter_option_2>
The filter options do not have any prefixes; if you need to refer to the options a filter accepts, you can hover over the filter name in your IDE of choice, or while editing in the XanoScript editor.
key-concepts-20251007-082259key-concepts-20251007-082404

Hover over the filter name to see the options it accepts.

This is a simple example of using a filter.
"hello"|capitalize
This is a simple example of using a filter with an argument.
"hello"|concat:"world":

Build Along: Using filters

Let’s use the format_timestamp filter to format the created_at value of a user so that it’s human readable.
Filter example -- Capitalize the name of a user
$user_item.created_at|format_timestamp:"Y-m-d H:i:s"

Loops

Loops are used to repeat a set of operations a certain number of times. XanoScript supports three different types of loops: For Each, For, and While. In the example below, $x1 is the variable that contains the list of items to iterate through. $x2 is the variable that will hold each item as the loop iterates through it. In the nested object, you can add any number of functions to be executed as part of the loop.
This is a simple example of a For Each loop.
foreach ($x1) {
  each as $x2 {
    util.sleep {
      value = 1
    }
  }
}
In the example below, 10 is the number of times the loop will run. $index is the variable that will hold the current iteration number, starting at 0.
This is a simple example of a For loop.
for (10) {
  each as $index {
    util.sleep {
      value = 1
    }
  }
}
For a while loop, in the example below, <condition> is the condition that will determine if the loop will continue to run, for example $x1 = 10 would continue to run the loop while $x1 equals 10. For statements that evaluate to true, you don’t need to specify the condition, as it will be assumed to be true by writing while ($x1) if, for example, we were checking to see if $x1 was equal to true. For statements that evaluate to false, you need to specify the condition, as it will be assumed to be false by writing while ($x1 == false). You can specify multiple conditionals by using the && operator for AND, or the || operator for OR. You can also use grouping to specify AND() and OR() groups, such as while ($x1 == true && (1 < 2 && 2 > 3)).
This is a simple example of a While loop.
while (<condition>) {
  each {
    util.sleep {
      value = 1
    }
  }
}
In the example below, we’ll loop through our list of users and manipulate the data as we go.

Build Along: Using a loop

Let’s use a loop to iterate through our list of users and manipulate the data with filters. The end result will give us a list of users with their names capitalized and their created_at values formatted.
This is a simple example of a For Each loop.
    foreach ($users) {
      description = "Iterate through each user record to apply formatting."
      each as $user_item {
        var $formatted_item {
          description = "Apply formatting expressions to the name and created_at fields."
          value = $user_item|set:"name":($user_item.name|to_upper)|set:"created_at":($user_item.created_at|format_timestamp:"Y-m-d H:i:s")
        }
      
        array.push $formatted_users {
          description = "Add the newly formatted user object to the results array."
          value = $formatted_item
        }
      }
    }

Conditionals

Conditionals are used to perform different actions based on the condition of a statement. XanoScript supports three different types of conditionals: If, Else, and Else If.
Conditional statement structure
conditional {
  if (<condition>) {
    ...
  }
  elseif (<condition>) {
    ...
  }
  else {
    ...
  }
}

Build Along: Using a conditional

Let’s use a conditional to check and see if any users were returned. If not, we’ll update the formatted_users variable to contain an error message.
Conditional example -- Check if the users were returned
    conditional {
      if ($formatted_users|is_empty) {
        var.update $formatted_users {
          value = "No users returned!"
        }
      }
    }

Basic Examples

Get a user record by its ID

key-concepts-20251007-085201
Get a user record by its ID
query user/{user_id} verb=GET {
  description = "Get user record"
  input {
    int user_id? filters=min:1
  }

  stack {
    db.get user {
      field_name = "id"
      field_value = $input.user_id
    } as $model
  
    precondition ($model != null) {
      error_type = "notfound"
      error = "Not Found"
    }
  }

  response {
    value = $model
  }

  history = {inherit: true}
}

Get a user record, capitalize the name, and make the created_at field human readable

key-concepts-20251007-085915
Get a user record and manipulate the data
query user_list verb=GET {
  description = "Returns a list of all users with formatted names and creation dates."
  input {
  }

  stack {
    db.query user {
      description = "Get all records from the user table."
      return = {type: "list"}
    } as $users
  
    var $formatted_users {
      description = "Initialize an array to hold the formatted user data."
      value = []
    }
  
    foreach ($users) {
      description = "Iterate through each user record to apply formatting."
      each as $user_item {
        var $formatted_item {
          description = "Apply formatting expressions to the name and created_at fields."
          value = $user_item|set:"name":($user_item.name|to_upper)|set:"created_at":($user_item.created_at|format_timestamp:"Y-m-d H:i:s")
        }
      
        array.push $formatted_users {
          description = "Add the newly formatted user object to the results array."
          value = $formatted_item
        }
      }
    }
  }

  response {
    value = $formatted_users
  }

  history = {inherit: true}
}
I