Botsquad documentation logo

The Botsquad chatbot language is a simple programming language which you use to program chatbots in the Botsquad environment. We refer to this language as the DSL, which stands for “domain specific language”. This introduction helps you get started with building your own chatbot.

Lets start off with a simple example:

dialog main do

  say "To the optimist, the glass is half-full."

  say "To the pessimist, the glass is half-empty."

  say "To the engineer, the glass is twice as big as it needs to be."
  pause 10

  ask "Did you laugh?", expecting: ["Yes", "No"]
  if answer == "Yes" do
    say "You must be an engineer!"
    say "You're not an engineer are you?"


Hopefully you already get the gist of what this bot will do. Don’t worry if you don’t. In the following paragraphs the scripting language used will be explained in more detail.


Dialogs are the main building blocks of bot conversations. Dialogs contain interactions with the user, grouped together and ran in sequence, one after the other. The main interactions are say, ask and show.

dialog main do
  say "Hi there!"
  say "I'm a chatbot"

Interactions (like ‘say’, ‘ask’, ‘show’) can only be used inside dialogs, not outside of them.

Do … end blocks

Blocks (everything starting with do and ending with end) are another fundamental construct within the DSL. They hold multiple interactions (statements and expressions) that are run in sequence when they get “in the path of execution”.

We have already seen blocks in both the dialog:

dialog welcome do
  say "Welcome! nice to meet you"

as well as in conditions:

  if answer == "Yes" do
    say "You must be an engineer!"
    say "You're not an engineer are you?"

Every do needs to have a closing end. This can be tricky with nested blocks. The editor will help you by automatically indenting.

Named dialogs

Dialog can have names, these names are used to refer to dialogs from other dialogs.

In the above earlier script we named the dialog main. This name, main, has a special meaning: the main dialog dialog is started automatically when the bot starts. Each has exactly one main dialog.

The name of a dialog can be used to start (invoke) another dialog:

dialog main do
  invoke welcome
  say "How can I help you?"

dialog welcome do
  say "Welcome! Nice to meet you."
  say "I hope you have a wonderful time here."

In this example the bot will start in main, but will then immediately start welcome. After welcome is done, it continues in main, saying “How can I help you?” last.

Message triggers

After the main dialog finishes new dialogs get triggered by matching the users input to dialog triggers:

dialog trigger: "hi" do
  say "hi there!"

The above dialog will be invoked when the user types “hi” anywhere in a sentence: “hi there” or just “just wanted to say hi”. Note: Dialogs are matched from top to bottom so order matters.

dialog trigger: "say hi" do
  say "thats nice of you"

dialog trigger: "hi" do
  say "hi there"

The trigger: "say hi" is first matched so “just wanted to say hi” and will match this dialog and not any of the dialogs after it like trigger: "hi".

Besides exact matches you can also match text patterns.

dialog trigger: "mus(ic|eum)" do
  say "you typed either music or museum"

The full message that the user typed is availabe in the variable message. The exact part that matched the trigger is available under the variable entity.value.

dialog trigger: "mus(ic|eum)" do
  say "you typed '#{message}'' matching '#{entity.value}'"

Note that matches are case insensitive: “museum”, “Museum”, “MUSEUM” will all match the dialog(trigger: “museum”).

Responding to the user

We have already seen how to respond to users with text using say and ask.


  say "Hello!"

Next to normal text you can also any UTF-8 characters as well as emoji

  say "😂 Very funny!"

Type / Pause

To send a reply with an extra delay you add a type (showing a typing indicator) or pause (silent) statement after the message

  type 2
  say "Hello"
  pause 2
  say "world"

type 2 will show the typing indicator for 2 seconds, pause 2 will not show a tying indicator but just wait for 2 seconds before continuing.


To give a random response use random with a block of potential responses:

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

Just saying random things to user would not make for a good dialog. Lets ask the user something…


To ask a user for input you can use ‘ask’ and assign the result to a variable:

  name = ask "What is your name?"
  say "Hello, #{name}!"

When encountering an ask, the bot will wait until the user types a message. In the above example, the captured reply is assigned into the variable name.

You can also use ask without variable assignment:

  ask "What is your name?"

The result is then stored in the variable answer.

Validation: expecting a certain answer

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

  if answer == "Yes" do
    say "You must be an engineer!"

When using expecting:, you can enforce the user giving a certain type of answer: the ask statement will retry until the expectation has been met.

When using , expecting: ["Yes", "No"] it will render quick-reply buttons for “Yes” and “No” and only allow those exacts answers. It will ask again when the user types anything else.

Asking with a more fuzzy expectations

To make the bot a bit more forgiving (smart) you can use entities in the expecting:

dialog question do
  laugh = ask "Did you laugh?", expecting: [@yes, @no]

  if laugh == :yes do
    say "You must be an engineer!"

Note that when using entities you should match against the returned atom :yes instead of the string “yes”. Think of :atoms as names for things dynamically provided by the system, where variables are names for things you/the user provide.

The defaults script that is added when you create a bot on Botsquad contains (amongst others) the entities definitions for @yes and @no. They are not magic at all, if you look at them:

@yes      learn(input: "yes|yep|sure", label: "Yes", return: :yes)
@no       learn(input: "no|nope|nah", label: "No", return: :no)

Read more about entities (@yes), string matching and learning in the NLP section.

Quick Replies

Sometimes you want to show quick-reply buttons but not restrict the user to only these values. In that case you can use quick_replies:

  name = ask "What is your name?", quick_replies: ["skip"]
  if name == "skip" do
    name = "Anonymous"

or even combine it with an expectation:

  laugh = ask "Whats your email?", quick_replies: ["yep", "nah"], expecting: [@yes, @no]

the quick_replies will overwrite what the expecting would set as quick_reply. So in this case without quick_replies it would show the quick replies “yes” and “no”, with the quick_replies it renders “yep” and “nah”.

Quick replies can also contain an image:

  ask "Please choose your option", quick_replies: [
    [title: "Main menu", image_url: ""],
    [title: "Pricing", image_url: ""] ]

All of the above is supported on the Web, Facebook, Telegram and Slack, with the exception that on Slack and Telegram, quick reply images do not work.

Ask timeout

Sometimes, the user might not want or forgets to respond to your question. For these cases you can add a timeout to the ask, so that the script continues after a short while.

  name = ask "Whats your name?", timeout: 30

This will wait 30 seconds before it continues. When the ask times out, the returned value is nil, so you can use a if !answer do... construct to check whether a timeout was triggered.

You can also use the global attribute @ask_timeout to give every ask statement a default timeout value:

@ask_timeout 30

dialog ask_name do
  name = ask "Whats your name?"
  if ! name do
    say "Never mind then!"

When you want a different value to be returned when the question times out, you can use the timeout_value option.

  name = ask "Whats your name?", timeout: 30, timeout_value: "John Doe"

But, enough said about text…

Sending an image

To send an image back to the user at a given url:

  show image ""

Other user interface elements

It is possible to present other user interface elements to the user, for instance a list of buttons, a gallery with images, an item list, et cetera. See Chat interface elements for the full manual on how to use these.


To allow the bot to make decision based on the users input the statement IF and ELSE are used to test predicates and conditionally execute blocks of code.


with if you can test for truthiness of a condition (predicate).

  if platform == "Yes" || app == "Yes" do
    say "Apps or platforms are not really a thing anymore."

Reads: “If variable platform is equal to “Yes” and variable app is equal to “Yes” do the following”


Executes the else block if the if statement evaluates to false (is not true).

  if exits != "Yes" do
    say "I believe in your product. Let's do it!"
    if team > 3 do
      say "I think your team is too big."
      if platform == "Yes" || app == "Yes" do
        say "Apps or platforms are not really a thing anymore."

You can nest if and else statements as deep as you want; for sanity it’s wise to keep it to max 3 levels deep. To circumvent nesting of branches, use the branch statement described below.


The branch statement evaluates each expression that it finds, until the first one that evaluates to true. It then executes all the statements inside the block.

This is especially handy if you want to branch the conversation on more than two branches:

  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!"

In the above example the expecting: assures that answer can only contain the 3 cases we branch on. If you branch on an open answer you can end with a catch all true case:

  ask "What is your name?"
  branch do
    answer == "Steve" -> say "hi Steven"
    answer == "Pete"  -> say "hi Peter"
    true              -> say "hi stranger"

Operators in predicates

The following operators can be used:

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


Variables in Botsquad script are global meaning they are available from any file and or dialog. Some variables are made available by the systems, others are created by the user in the scripts by assignment. All variables are can be seen in the panel below the script editor.


  {http responses}

  {script variables}

Note that you can create these object maps yourself as well:

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

runtime variables

Some variables are set (when triggered) by the runtime itself:


Don’t set these reservered variables yourself as they will be overwritten!

remember variables

Variables are used to provide context for the conversation and steer the dialogs. Some variables are temporary and can be forgotten the next time the user engages with the chatbot while other variables (name, preferences) you want the bot to remember. To remember variables just say remember variable. Like wise to forget remembered information you can say forget variable

dialog greeting do
  if visited_before == true do
    say "hi again!"
  visited_before = true
  remember visited_before

  if user.first_name do
    say "Nice to meet you #{user.first_name}"
    user.first_name = ask "whats your name?"
    say "Nice to meet you #{user.first_name}"
    remember user.first_name

Tagging sessions

It is possible to automatically tag chat sessions by using the tag statement. The tags can be used to indicate that a user has passed through a certain piece of code. For instance, when a user leaves his email address, you could have the script tag the session as a lead (tag "lead"). Any expression can be evaluated in the tag statement. Similarly to tag, there is also an untag.

dialog collect_email do
  email = ask "Please enter your email", expecting: [:email, "Skip"]
  if email != "Skip" do
    remember email
    tag "lead"
    say "Thanks!"
    say "No worries!"
    tag "anonymous"

Styling of messages

You can add a class: option to both say and the media tags. This class gets propagated into the action output, so you can use it (e.g. in the webclient) to style the received message.

dialog styling do
  say "Greetings!", class: "large"
  say "P.S.: did you notice we have a new site?", class: "note"
  image "", class: "rounded"

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


capturing dialogs

You can also capture parts of the user’s input in a dialog by using a notation with pointy brackets, specifying the variable name between the brackets as follows:

dialog trigger: "My name is #<name>" do
  say "Hi #{}, how are you?"

You can even capture multiple parts of a message in a triggers:

dialog trigger: "My #<object> is #<color>" do
  say "So you have a #{entity.color} #{entity.object}?"

These brackets need to be different from the normal string interpolation brackets #{var}, because the capturing trigger is evaluated each time a trigger runs.

You can also use regular expressions in the capturing group as follows:

dialog trigger: "My age is #<age:\d+>" do
  say "so you are #{entity.age} years old!"

which then would only match when the users enters one or more digits as an age.

__unknown__ dialog

If none of the defined dialogs match the user’s message a special dialog __unknown__ will be triggered.

dialog __unknown__ do
  say "Sorry don't understand #{message}. I am just a robot you know 😂"

the variable message hold the full message the user typed.


After returning to the previous dialog on the stack the dialog __returning__ is invoked:

dialog main do
  say "Hi"
  pause 60
  say "there"

dialog __unknown__ do
  say "?"

dialog __returning__ do
  say "Back to the subject."

If you type anything here if will call __unknown__ and then __returning__ when returns to the main dialog to continue stepping through the script.

Advanced responses / text matching

Intent matching

@hello intent(learn: ["hi", "hello there", "howdy", "how are you doing"])

dialog trigger: @hello do
  say "Hello to you too!"
  say intent

Entity extraction

The entity() function allows you to define entities which can be used in ask and in dialog triggers. Some defaults are defined in every BotSquad bot. These entities are defined in the defaults file and match multiple user inputs returning atoms :yes or :no

@yes entity(match: "yes|yep|sure", label: "Yes", return: :yes)
@no entity(match: "no|nope|nah", label: "No", return: :no)

Besides @yes and @no the following entities are also already defined in the defaults file. @postcode and @email use a ‘regular expressions’ to pattern match the user’s response. If it matches the user’s message will be accepted if it does not it will loop and if defined the help_dialog will be invoked.

@postcode entity(match: "^\d{5}(?:[-\s]\d{4})?$")

dialog main do
  laugh = ask "What is your postcode?", expecting: @postcode, help_dialog: invalid_postcode

dialog invalid_postcode do
  say "Sorry, that doesn't look like a postcode. It should be something like 1000 AA."

To learn more about regular expression matching you can use this online tool: regexr

Entity recognition with Duckling

Botsi integrates with Duckling so that you can use duckling’s entity matchers on the user’s input.

For this a special syntax is allocated: entity(duckling: "email").

@email entity(duckling: "email")
dialog duckling do
  email = ask "What is your email?", expecting: @email
  say email.value

The duckling matchers are smart and only extract the text which is relevant to the given entity. So when asking for an email address, the user can respond with "My email address is [email protected]!" and the email.value will only contain [email protected].

You can also use dialog triggers with duckling, like this:

@email entity(duckling: "email")
dialog trigger: @email do
  say "Thank you for your email! You entered: #{value}"

The following is a list of all supported entities:

Note that not all entities are supported in every language. Please refer to the Duckling documentation for exact support.

Duckling matchers are locale and time zone sensitive. By default, the locale is en_US and the time zone is Europe/Amsterdam. These can be changed by passing them in as arguments to the entity() function like this:

    @time entity(duckling: "time", locale: "nl_NL", timezone: "UTC")

Handling validation errors

To handle validation dialogs you can use the following: To direct the user on typing or clicking “Yes” or “No” you can also add a help dialog:

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

dialog help do
  say "Please click the quick replies or type 'Yes' or 'No'"

To create a gallery you can use the template statement:

  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"

  # ...

Asking for an image

To ask a user to upload an image you can use the expecting: with :image as expected value.

dialog image do
  image_url = ask "Please upload a selfie", expecting: :image
  say image_url
  image image_url

The uploaded image url will be captured into the variable image_url and can be used later to send the same image back to the user.

Receiving images

You can also create a dialog that responds when a user sends the bot an image at any time (so not when being asked like above).

dialog attachment: :image do
  say "thanks for your image!"
  image attachment.url

The following variables are created by the system when a file is received:

attachment.type image

Receiving other media types

To respond to any other media type the user uploads you can use a special system dialog:

dialog __unknown_attachment__ do
  say "Thanks for the #{attachment.type} file"

Asking a location

To ask a user to provide his location you can set :location as expectation.

  location = ask "Please share your location", expecting: :location

The location latitude and longitude will be captured into the variable location. This is an Array with two elements and can be accessed as follows:

  lon = item(location, 0)
  lat = item(location, 1)
  say "your latitude is #{lat}, and longitude is #{lon}"

Distance functions

  d = distance([4.893414, 52.364909], [4.8952, 52.3702])

Sending a location

Similar to images you can also send a small map with a location:

  location [4.893414, 52.364909]


To get the first element of the location Array we use the helper function item passing the location array as first argument and index as second. Note: arrays in most programming languages start at 0 zero!

As we say above to get a n-th item from an array you use:

dialog n do
  list = [1, 2, 3]
  say item(list, 1)
  length([1,2,3]) == 3

To iterate over the full array you can use repeat:

dialog iterate do
  list = [1, 2, 3]
  repeat item in list do
    if item == 3 do
      say "and.."
    say "item #{loop}"

Location matchers

A script can respond to the user sending his location (outside of an ask)

dialog(location: [4.893414, 52.364909, 200]) do
  say "Welcome in Amsterdam!"


When you define a single dialog named __unknown_location__ it will be called whenever the user sends a location that is not matched by any dialog with a location: matcher.

dialog __unknown_location__ do
  hilversum = [5.177459, 52.224025]
  d = round(distance(location, hilversum) / 1000)
  say "You are #{d} km from Hilversum"

Emit an event

The emit statement sends an event to the runtime that is executing the bot. These events can be used for instance to trigger backchannel events or trigger events on the website.

The event itself is a string; it can have an optional extra parameter which will be sent as payload.

dialog main do
  emit "start"
  say "Hello!"
  emit "end", [user_id:]

The event name can be a simple string or an expression. The payload is optional and can be any valid expression.

You can use events to trigger javascript on the page where the chatbot is hosted:

dialog events do
  emit "focus", [element: "#section-1"]

You do need to respond to these events by adding handlers in the integration Javascript.


Defaults are set via the following @attributes:

@delay 1
@typing_indicator 1

To speed up dialogs for testing purposes you can set them to 0

@delay 0
@typing_indicator 0

This way you don’t have to wait between statements.

When there is no user interaction the chatbot will “die” after a certain time. This can be set via the attribute @timeout

@timeout "1h"

The notation in the string is as follows:

@timeout "1h"
@timeout "60s"
@timeout "200ms"


Just before the chatbot dies a special dialog is invoked allowing you to do a last interaction with the user.

dialog __timeout__ do
  say "Thanks for you visit"
  buttons "We would love to hear your opinion on the experience" do
  "Share your opinion" ->
    open "{user.first_name}"

You can for instance use this to ask visitor to participate in a survey. Here the survey is done by linking to another bot. Note you can pass information to the next bot via parameters. You can capture this information in the next bot via the query variables:

dialog main do
  user.first_name =
  chatbot =