Skip to content


The dialog is the main building block of conversations. All statements should be contained in a dialog.

dialog main do
  say "hello"
  ask "what is your name?"

Dialog triggers

Dialogs can have triggers which are matched against user input to trigger the dialog. Dialogs are evaluated in order, meaning the first dialog that matches wins.

dialog trigger: "string" do


Match sentences which contain the word "string". The original message that caused the trigger is accessible in the variable named message. The part of the string that was matched is stored in the variable entity.

dialog trigger: "my name is #<name>" do


Match sentences with "my name is" capturing everything after into variable

dialog trigger: "stop|quit|end" do


Match sentences with the word "stop" or "quit" or "end"

dialog trigger: "num:#<n:[0-9]+>" do


Match "num:" followed by a number and capture the number into variable n


dialog location: [lon: 4.893414, lat: 52.364909], radius: 3000 do
  say "Welcome in Amsterdam!"

Match any location within a radius of 200 meters from position


dialog attachment: :image do
    show image attachment.url

Match any message with attachment of type image


dialog event: "name" do


Match on incoming events from client, webhook or other bots labaled "name"

special dialog names

dialog __root__ do


the root dialog is invoked the moment the bot's stack is cleared. Implement __root__ to create a "menu"-like interaction model where the bot keeps returning to a menu of options when it is done processing its current dialog.

dialog __unknown_location__ do


invoked when no other location trigger matches

dialog __unknown__ do


invoked when no other dialog matches

dialog __returning__ do


invoked when returning

dialog __timeout__ do


Invoked when session timeout is reached. The standard session timeout is 15 minutes; it can be changed setting the @timeout constant.

dialog __timeout__ do


Invoked when ask_timeout is reached. The standard session timeout is infitity (never); it can be changed setting the @ask_timeout constant.

Dialog guard clauses

Dialogs can have "guard clauses"; small expressions which can be added to the dialog definition. These guards decide whether the given dialog is matched or not:

dialog main when user.frontend == "slack" do
  say "Hello slack user!"

dialog main do
  say "Hello, other user"

There can be multiple dialogs with the same name or trigger, but with different guard clauses. When looking up a dialog, the guard clauses get evaluated and the first dialog with a true guard clause gets executed. Dialogs are processed in order, from top to bottom of the files.


Variables starting with an @ are treated as constant values. They can only be defined outside dialog.

@constant "key"
@version 12
@message "Hello and welcome!"

dialog main do
  say @message
  say "My version is: " + @version

Special constants

The following constants have special meaning, when they are defined. - @ask_timeout - defines how long to wait for a user to give an answer on a question (ask) - @timeout - defines how long the script will be allowed to run before it times out. When the script times out, the next time the user interacts with the bot, a new session is started (starting again in the main dialog). - @delay - defines the standard delay (in seconds) after the bot shows a message to the user - @typing_indicator - defines the standard delay (in seconds) how long the typing indicator should be displayed before any message is shown to the user.


@ask_timeout      :infinity
@timeout          "15m"
@delay            1
@typing_indicator 1

Loading constants from a file

When adding a YAML or a JSON file in the Botsqud studio, the contents of that file is exposed in a constant which is named after the JSON/YAML script file name.

Data files are a convenient way to keep data outside the bot script contents, but still embedded as part of the bot.

For instance, when you create a YAML file called people with the following contents:

- name: Arjan
  age: 38
- name: Pete
  age: 22
- name: Julie
  age: 32

The contents of the people file is now accessible in your bot script under the constant @people. It can be used like this:

repeat person in @people do
  say "Name: " +
  say "age: " + person.age

When a data file is defined in a folder, say, a data file named data/people, the slash in the filename will be transliterated into an underscore, so that in this case, the constant name becomes @data_people.

Read more about data files and content management

Accumulating constants

It is possible to create a list of constant values which is assembled over multiple scripts, by using a special accumulate syntax:

Script one contains:

accumulate @a, "hello"

Script two contains:

accumulate @a, "world"

When used in dialogs, the final value of the constant @a will now be the list of ["hello", "world"]. This construction is used to build composable lists of intents or values.

Statements reference


Invoke is used to switch to a different dialog. It can be compared with the 'go to' statement, with the exception that it uses a stack, so after the called dialog is finished, the execution will return on the place after the invoke statement.

invoke mydialog

A "vanilla" invoke adds (pushes) the dialog on top of the current dialog stack, returning there after finishing.

Note that you can not use expressions in the name of the dialog, they always need to point to an existing dialog in one of the bot's scripts. The studio will show an error message when you have an invoke which points to a non-existing dialog.


invoke mydialog, :replace

Giving the :replace option to invoke will cause it not to return to the calling dialog, it will replace the current dialog in the stack.


invoke mydialog, :reset

Giving the :reset option to invoke will cause the bot to stop after finishing the called dialog, it clears the dialog stack entirely.

invoke dialog with a message trigger

dialog main do
  invoke message: "my name is Pete"

dialog trigger: "pete" do
  say "Hi Pete!"

This will cause the entered message (which can be an expression) to be handled by the global dialog message matching system, as if a user typed it. So the above main dialog will output "Hi Pete!" when executed.

invoke dialog with message trigger, resetting the stack

Resets the stack (just like invoke dialog, :reset would), and then performs the message matching just like `invoke message: "message" would.

dialog main do
  invoke reset: "my name is Pete"


Directly stops the interaction with the bot, ending the chat session and the bot process. The next time the user talks, a new chat session is started.

dialog trigger: "bye" do
  say "Thank you for your time."
  say "This line is never executed"


Stops the current dialog and clears the dialog stack. When a __root__ menu is defined, it is invoked.

dialog trigger: "bye" do
  say "Thank you for your time."


Sends the given "string" to client.

say "string"

URLs in text are automatically linked, and newline characters (\n) can be used to create a line break:

  say "Click this link:\n"

You can use limited markdown formatting in messages as well, for doing bold, italic, et cetera. The following tags are supported:

say "** bold text **"
say "* italic *"
say "`monospace text`"


Use the class: option to specify a specific presentation for the message. This currently only works in the web client.

For example, this sets the class "large" on the message:

say "string", class: "large"

The predefined classes in Web client are:

  • large - font size 2x as large as normal
  • system - gray, smaller text, no bubble around text


To let the bot impersonate another user, it is possible to specify the as: option, with a given user object.

The object needs to have at least an user_id and a first_name attribute. profile_picture can be used to specify the avatar.

person = [user_id: "arjan", first_name: "Arjan", profile_picture: "http:///..."]
say "Hello my name is Arjan", as: person

Messages like this will still appear on the left side of the chat window, and thus are meant for simulating users other than the current user.

as: :user

To let the bot impersonate the current user:

say "Hello from yourself", as: :user

These messages will appear on the right side of the chat window.


To specify how long the typing indicator should show before the say:

say "This takes long", typing_indicator: 20


To specify how long the delay should be after saying the message:

say "one ", delay: 0
say "two", delay: 0
say "three", delay: 0

Note that with delay: 0 you still see the typing indicator. However, delay: 0 can be combined with typing_indicator: 0 if you require so.


ask presents a text message to the user, and then blocks the input, to let the user type a response. When no variable name is given, the response of the user is stored in the variable named answer.

ask "how old are you?"
say answer

When used in an assignment, it is possible to store the response in another variable, like in the next example:

age = ask "how old are you?"
say age


The expecting: option will repeat the question until the message is matched against the value(s) in the expecting option.

This can be used for validation purposes, when you need the user to type something.

# Builtin replies "Yes", "yep", "ok", et cetera
ask "Did you laugh?", expecting: [@yes, @no]

# E-mail regular expression
ask "What is your email?", expecting: :email

# File upload
ask "Which file do you want to upload?", expecting: [:file]

# Fixed list of answers, presents them as quick replies
ask "Choose your beverage", expecting: ["Pepsi", "Sprite"]

Builtin types for expecting are: - :text - :email - :file - :location


The help dialog is invoked when a user enters something that does not match the expectation:

dialog asking do
  ask "Did you laugh?", expecting: [@yes, @no], help_dialog: help

dialog help do
  say "Please answer yes or no."


Quick replies give options for the user to choose from. However, these options are mere hints, they are not used for validation.

ask "Did you laugh?", quick_replies: ["Yes", "No"]

ask "What's your email?", quick_replies: ["no thanks"], expecting: [@email, @no]    # The `quick_replies:` are shown but `expecting:` is matched

ask "Please choose your option", quick_replies: [
  [title: "option 1", image_url: ""],
  [title: "option 2", image_url: ""] ]

It is possible to combine expecting: and quick_replies:; in that case, the given quick_replies are used but the validation is done on the expecting clause.


To prevent a script from "hanging", you can let the ask break out by giving a timeout value. The value wil be set to the given timeout_value, or nil if none was given.

ask "How are you?", timeout: 4, timeout_value: :yes, expecting: [@yes, @no]

if answer == :yes do
  ask "..."

It is also possible to trigger a dialog on a timeout. E.g. for asking the user to stop after being idle.

ask "How are you?", timeout: 4, timeout_dialog: ask_for_stop

ask "How are you?", timeout: 4

dialog __ask_timeout__ do
  say "Hey, wake up!"
  ask "Do you want to continue?"

Note, that when using ask from within a __ask_timeout__ dialog (without a timeout value), it will not trigger a new ask timeout dialog, but that ask will block infinitely.

To set a gloal timeout on all asks, use the @ask_timeout constant.


The await statement works similar to ask, except that no prompt or quick replies are shown while awaiting.

The first argument to await is the argument you would give to expecting: in an ask.

For example, the following waits for a valid email address and stores the value in answer.

await @email

The following blocks until an event with the name "click" has been fired:

await event("click")


Emits an event to the client, or to another chat process. An event consists of a named message (a string), plus an optional payload.

emit "status_update", text: "#{user.first_name} started using the bot"

By default, emit sends it event to the client, where it can be picked up by for instance the Javascript SDK (Botsqd.onEmit("event", ....)), but it can also emit to other processes, for instance the master process or a local group.

emit "status_update", [], to: "master"

Sends a message to the bot's master process. In that process it will be handled using the dialog event: "..." trigger.


These are the emit to: targets:

  • :client -- Sends the event to the code that is hosting the chat, e.g., the Javascript SDK. In the SDK, it can be caught using the onEmit() function. (this is the default value)
  • :master -- Sends the event to the bot'smaster process, if a master script was defined. See multiple processes.
  • "string" -- When the recipient is a string, it is considered a user ID, and sends the event there.

emit is "fire and forget" -- No warnings / errors are generated when sending of the message fails. If you need to check whether the message was arrived, use query.


Sends an event to the given user, and waits for the reply.

reply = query :master, "ping"
say reply

In the other script, define a task or dialog that catches the ping event, and use the reply construct to send a payload back:

task event: "ping" do
  reply "pong"

Make sure that the handling task / dialog in the other process finishes quickly. If there is no reply received within 1 second, the value :timeout is returned in the calling process.


Send a status_update event to the client, in 10 seconds from now, and 1 year from now

emit :status_update, [], in: 10
emit :status_update, [], in: "1y"

Specifying an absolute point in time is also possible:

emit :status_update, [], at: "tomorrow at 10:00"

This will use the duckling date parser to parse the time. If date parsing fails, a runtime error is thrown.

Note: It is not possible to schedule events in the past.

named emit

While scheduling you can give the emitted event a name that you can use to cancel it again. You can also use it to reschedule the event, because the name is unique; scheduling a second emit with the same name will cancel out the first (if it was not yet fired).

emit :status_update, [], in: 10, name: "update"
emit :status_update, [], in: 20, name: "update"

This will only send out one emit, in 20 seconds.

cancelling emits

A scheduled emit sets the variable with the name schedule_id in the bot script. You can use this variable to cancel it.

emit :status_update, [], in: 10, name: "update"
result = cancel_emit(schedule_id)


pause 3         # pause the dialog for 3 seconds


type 3          # pause for 3 seconds, while showing typing indicator


show is a generic way of showing media to the user.

show image ""

Note: The statements image, location, video and audio are actually macro's which use show to display the content; these will be deprecated in time.


show image ""


show image(""), class: "rounded"

Note: the parenthesis around the URL are required when using extra options like class.


show location [lat: 52.364909, lon: 4.893414]


say random 10

returns a random number (int) between 0 and 10

say random [1,2,3,4]

returns a random element from the list

random do
  say "Hi!"
  say "Hello!"
  say "Howdy!"


With once you can say things 'only once' stepping down a statement on every invocation.

once do
  say "Hi!"
  say "Hi again!"
  say "We meet again"

This can be used to only say 'Hi' the first time and second time say 'Hi again' for instance. Every itteration once will select the next statement down. When there is an after present the statement after will be invoked on every successive itteration. Without after the once block will not do anything (when exhausted)

You can provide a name to the variable that is used to store the counter. This can be used to reset it so that the once block cycles instead of getting exhausted.

once i do
  say "Hi!"
  say "Hi again!"
  say "We meet again"
  dialog.once.i = nil # reset this once block to start again (cycle)

remember / forget

remember is the way to persist information for a user over the bot's session. All information stored by remember is saved with the bot's user and can be viewed in the studio. In a next session, these variables will be set again when new session starts. This way, you only have to ask a user his name once, for example.

    variable = 123
    remember variable
    forget variable

NOTE: variables can be deep 'object maps':

car.brand = "Volvo"
car.engine.hp = 200

remember car

This remembers the object car and its nested values.

tag / untag

Tags or untags the current chat session. Can be used later for filtering and matching users.

tag "lead"
untag "lead"

Tags are availabe in the user object: user.tags contains an array of all the users's collected tags over all sessions.

To display the tag counts in the same graph in analytics (for funnel analysis) you can use a namespace

tag "funnel:1-signup"

All tags in the "funnel" namespace will be shown in analysis as Tags for funnel. To order the tags according to the funnel steps its best to number them.


Basic message logging for debugging purposes.

log "This is a log message"

Log messages can contain any valid expression and are logged in the output tab in the Botsquad studio.


if exits != "Yes" do
  say "Lets continue"


if exits != "Yes" do
  say "Lets continue"
  say "Ok lets quit"


A task is a construct which is comparable to a dialog. However, tasks are meant for non-interactive computations, therefore, no statements are allowed that perform user-level side effects, like say, ask, show.

Typically, tasks are executed when an event comes in:

task event: "ping" do
  counter = counter + 1

The advantage of using tasks over dialogs here is that tasks execute outside of the current dialog context. Therefore, the current dialog continues undisturbed, without any other effects (like __returning__).

Named tasks

Tasks can also have a name, just like dialogs.

task calculate do
  counter = counter + 1
  if counter > 10 do
    emit "full"


Analogous to invoke, a task can be executed by using the perform statement:

perform calculate

Tasks do not have arguments. As the DSL has a global scope, any parameters that are required inside the task should be defined just before the perform statement.


ask "Do you want to continue?", expecting: ["Yes", "No", "Maybe"]
branch do
  answer == "No" ->
    say "Sorry to hear"
  answer == "Yes" ->
    say "Great!"
  answer == "Maybe" ->
    say "Make up your mind!"


buttons "What do you want to do next?" do
  "Open CNN" ->
    url ""
  "Say hello" ->
    postback "say_hello"

NOTE: though buttons are similar to quick_replies they invoke dialogs via a postback event: rather than trigger: and are not displayed as user message in the conversation.


  template do
  "The hat shop 1" ->
    image_url ""
    subtitle "We've got the right hat for everyone."
    default_action do
      open ""

    buttons do
    "View Website" ->
      open ""
    "Say hello" ->
      postback "postback"

  "The hat shop 2" ->
    image_url ""
    subtitle "We've got the better hat for everyone."
    default_action do
      open ""

    buttons do
    "View Website" ->
      open ""
    "Say hello" ->
      postback "postback"

  # ...

= (assignment)

variable = ask "whats your name?"
variable = "string"
variable = 1
variable = [1,2,3]
variable = first [1,2,3]

NOTE: variables are global and mutable.

Operators reference

addition (+)

The + operator has superpowers.

  • It performs addition on numbers: 1 + 3 == 3
  • It concatenates strings: "a" + "b" == "ab"
  • It can glue lists together: [1] + [2, 3] == [1, 2, 3]
  • It can even glue lists together when one element is not a list: 1 + [2, 3] == [1, 2, 3]
  • It merges maps: %{"a" => 2} == %{"a" => 1} + %{"a" => 2}
  • When it encounters nil on either side of the +, it ignores it: [1, 2] + nil == [1, 2]

substraction (-)

The - operator is superpowered as well.

  • It performs regular substraction: 3 - 2 == 1
  • When encountering strings which are numbers, it converts them to numbers: "100" - "10" == 90
  • It can be used to remove elements from a list: [1, 2] - [1] == [2]
  • It can be used to remove substrings from strings: "paap" - "aa" == "pp"

In all other cases, it raises an error, for instance when you try to substract a number from a string.


a && b    # AND
a || b    # OR
a !  b    # NOT
a == b    # IS
a != b    # IS NOT
a <  b    # IS SMALLER
a >  b      # IS LARGER