Variables and data types
Variables hold the data a task works with: text read from a page, a number to add up, a list of files, a table from a database, a window or a browser page to act on. This page lists the types, how to declare and fill them, and how tasks pass values to each other.
Declaring a variable
Declare a variable with set, giving its --type and, if you like, an initial value:
set --name:customer --type:string --value:"ACME Ltd"
set --name:total --type:number --value:0
set --name:today --type:datetime --value:now
In the editor, use Add variable in the VARIABLES tab, or New variable in the Insert variable dialog (F2): Botream adds the set command at the top of the task for you.
- Names start with a letter or
_and contain only letters, digits and_(orderId,_temp2). When the task runs, names are not case-sensitive. setwithout--typechanges a variable that already exists and keeps its type:set --name:total --value:42.- Many commands create variables themselves through
--output, and loops through--variable. You do not need to declare those first.
Read a variable by writing its name between dollar signs: $customer$. See Variables in values for fields ($order.Total$), items ($items[0]$), .Count and .Length.
Types
| Type | Holds | Initial value (--value) |
|---|---|---|
string |
Text | Any text; may contain $variables$. Empty when not set |
number |
Whole or decimal number | 42, -3.14. 0 when not set |
boolean |
true or false | true or false. false when not set |
datetime |
Date and time | now, 2026-09-30, 2026-09-30 14:30 or 2026-09-30 14:30:00. The time the command runs when not set |
list |
Ordered items | A JSON array: ["a", "b", 3]. Empty when not set |
dictionary |
Key → value pairs | A JSON object: {"env": "prod", "retries": 3}. Empty when not set |
record |
One row of named fields | A JSON object: {"Name": "Ali", "Age": 30}. Empty when not set |
datatable |
Rows and columns | {"columns": ["Name", "Age"], "rows": [["Ali", 30]]}. Empty when not set |
file |
A file path | A path, such as C:\In\orders.xlsx |
window |
A desktop window | --title and/or --path instead of --value |
browser |
A browser page | Filled by browser --action:open --output |
credential |
A user name and password from a vault | --vault and --credential instead of --value |
Instead of a literal, any initial value can be a single reference to another variable (--value:$other$); it is converted to the declared type when the task runs. The editor checks literals as you type: set --type:number --value:abc is an error.
Text, numbers, booleans and dates
set --name:greeting --type:string --value:"Hello $customer$"
set --name:rate --type:number --value:0.18
set --name:isTest --type:boolean --value:true
set --name:deadline --type:datetime --value:"2026-12-31 17:00"
Numbers use a dot as the decimal separator. Use the string command to convert text: --action:tonumber, --action:todatetime --format:dd.MM.yyyy, --action:toboolean; number --action:tostring and datetime --action:format turn values back into text.
Lists
set --name:countries --type:list --value:["DE", "AT", "CH"]
list --action:add --name:countries --value:FR
log --message:"First: $countries[0]$, total: $countries.Count$"
loop --type:list --list:$countries$ --variable:country
log --message:$country$
endloop
JSON literals are written without quotes around the whole value, even with spaces inside. References may appear in them: ["$first$", "b"].
Dictionaries and records
Both are written as a JSON object and read with a dot: $config.env$, $person.Name$. A dictionary is a free set of keys; a record is one row of named fields, as loops over Excel rows, data tables, files and queue items give you. Field names are matched without regard to case.
set --name:config --type:dictionary --value:{"env": "prod", "retries": 3}
set --name:person --type:record --value:{"Name": "Ali", "Age": 30}
dictionary --action:addkey --name:config --key:region --value:eu-west
log --message:"$person.Name$ ($person.Age$) on $config.env$"
A key with spaces or other characters cannot follow a dot; read it with getvalue:
dictionary --action:getvalue --name:config --key:"Order No" --output:orderNo
Data tables
A data table has named columns and rows. Write it as columns and rows, or as a list of objects with the same keys:
set --name:people --type:datatable --value:{"columns": ["Name", "Age"], "rows": [["Ali", 30], ["Veli", 25]]}
set --name:prices --type:datatable --value:[{"Item": "Pen", "Price": 1.5}, {"Item": "Pad", "Price": 3}]
Column names must be unique. In the editor, Create table opens a grid with typed columns and Paste from Excel.
$people[0].Name$reads a cell,$people[1]$a row (a record),$people.Count$the number of rows.loop --type:datatable --table:$people$ --variable:rowruns once per row; read cells as$row.Name$.capture --action:gettable,database --action:query,csv --action:read,excel --action:readrangeandjson --action:todatatablereturn data tables.- Join, group, look up and total them with the
datatableactions; see Joining, grouping and looking up data tables.
Files
A file variable holds a path. loop --type:eachfile gives each file as a record with Name, NameWithoutExtension, Extension, Path, Directory, Size, CreatedDate and ModifiedDate; loop --type:eachfolder gives Name, Path, Parent, FileCount and CreatedDate.
set --name:report --type:file --value:C:\Reports\daily.xlsx
loop --type:eachfile --folder:C:\In --filter:*.pdf --variable:pdf
log --message:"$pdf.Name$ is $pdf.Size$ bytes"
endloop
Windows
A window variable describes which desktop window commands act on. Declare it with a title and/or a program path; * is a wildcard in both:
set --name:notepad --type:window --path:"C:\Windows\notepad.exe" --title:"*Notepad*"
sendkeys --window:$notepad$ --keys:"Hello[ENTER]"
Or start or attach to a program and receive the window:
window --action:open --path:C:\Apps\erp.exe --output:erp
window --action:attach --title:"*Invoice*" --output:invoiceWindow
window --action:getproperty --window:$erp$ --property:Title --output:erpTitle
window --action:getproperty reads Title, Path, ProcessId, X, Y, Width, Height, IsMinimized, IsMaximized and Handle. In the editor, New next to a window field creates a window variable from one of the windows open on the agent machine. See Desktop automation.
Browser pages
browser --action:open --output:shop opens a browser and puts its page into $shop$; every web command then targets it with --browser:$shop$. Without --browser, a web command uses the page used last.
browser --action:open --url:"https://shop.example.com" --output:shop
capture --browser:$shop$ --action:click --dompath:"#login"
Warning:
set --type:browseralone holds no page. The editor warns when a--browser:$page$variable is never filled bybrowser --action:open --output. See Web automation.
Credentials
A credential variable reads a user name and password from a vault when the set command runs:
set --name:login --type:credential --vault:Production --credential:PortalUser
capture --browser:$portal$ --action:settext --dompath:"#user" --value:$login.Username$
capture --browser:$portal$ --action:settext --dompath:"#password" --value:$login.Password$
The password is never written to logs: wherever it would appear (log lines, error messages, the error variable of a catch, task outputs) it is replaced with ***. Commands with a --credential parameter (such as mail and restapi) take the whole variable: --credential:$login$; mail also takes the vault and the credential's name directly (--vault:"Mail" --credential:"Robot mailbox"). Whether a run may read a vault depends on the user who started it; see Vaults.
Command outputs
Commands that produce a value write it into the variable named by --output. The type depends on the command:
| Command | Output |
|---|---|
restapi |
A record: StatusCode, IsSuccess, Body, Json, Headers, ContentType, ElapsedMs, File |
capture --action:gettable, database --action:query, csv --action:read, excel --action:readrange, json --action:todatatable, datatable --action:join/groupby/distinct |
A data table |
capture --action:gettext, getproperty, ocr --action:read, clipboard --action:gettext, ai --action:ask |
Text |
browser --action:open / window --action:open, attach |
A browser page / a window |
mail --action:read (each item is what loop --type:mail gives) |
A list of records: Id, From, FromName, To, Cc, Subject, Date, Body, HtmlBody, IsRead, Attachments |
queue --action:next (and loop --type:queue) |
A record: Found, Id, Reference, Data, Attempt, Priority, Status |
pdf --action:getinfo |
A record: PageCount, Title, Author, … |
ocr --action:find |
A record: Found, Text, X, Y, Width, Height, CenterX, CenterY |
mouse --action:getposition |
A record: X, Y |
ai --action:extract / classify |
A record of the requested fields / Label, Confidence, Reason |
messagebox |
The button chosen: yes, no, ok, cancel or timeout |
restapi --action:get --url:"https://api.example.com/orders/$id$" --output:response
if --condition:$response.IsSuccess$
log --message:"Status $response.StatusCode$ in $response.ElapsedMs$ ms"
endif
See the command reference for every command's output.
Working with variables: string, number, list…
Each variable type has its own command, with only the actions that fit it: string, number, boolean, list, dictionary (also records), datetime and datatable. --name is the variable worked on; in the editor its list shows only variables of that type. With --output the result goes into another variable; without it, the variable is updated in place. Actions that give something else back (a length, true/false, an item) need --output.
number --action:increment --name:count
number --action:increment --name:count --by:5
string --action:uppercase --name:code
string --action:replace --name:text --old:";" --new:"," --output:cleaned
string --action:split --name:line --separator:";" --output:parts
datetime --action:adddays --name:today --value:-1 --output:yesterday
datatable --action:filter --name:orders --column:Total --operator:">" --value:1000 --output:bigOrders
| Command | Actions |
|---|---|
| string | trim, trimleft, trimright, uppercase, lowercase, titlecase, replace, substring, between, concat, insert, remove, padleft, padright, repeat, reverse, split, length, isempty, isnumber, contains, startswith, endswith, indexof, lastindexof, regex, regexmatch, regexreplace, format, base64encode, base64decode, urlencode, urldecode, hash, tonumber, toboolean, todatetime |
| number | increment, decrement, add, subtract, multiply, divide, modulo, power, min, max, round, floor, ceiling, truncate, abs, sqrt, random, format (e.g. {0:N2}), tostring |
| boolean | toggle, not, and, or, tostring |
| list | add, insert, setat, remove, removeat, clear, getat, count, isempty, contains, indexof, sort, reverse, join |
| dictionary | addkey, removekey, getvalue, containskey, keys, values, count, isempty, clear |
| datetime | now, parse, adddays, addhours, addminutes, addseconds, addmonths, addyears, datediff, getpart, format |
| datatable | addrow, removerow, getrow, getcell, setcell, rowcount, columncount, columns, addcolumn, removecolumn, getcolumn, filter, sort, reverse, isempty, clear, join, distinct, groupby, lookup, aggregate |
The command reference lists the parameters of each action. Tasks written with the older all-in-one variable command (variable --action:uppercase …) keep working unchanged; new blocks use the commands above.
Joining, grouping and looking up data tables
Reports and reconciliations — sales per region, invoices matched to payments, employees without a manager — are a few datatable blocks. Each gives a new data table (or a value); without --output the table is replaced.
datatable --action:join --name:orders --with:$customers$ --on:"CustomerId=Id" --type:left --output:ordersWithCustomers
datatable --action:distinct --name:applicants --columns:"Email" --output:uniqueApplicants
datatable --action:groupby --name:sales --columns:"Region" --aggregates:"Total=sum(Amount), Orders=count(), Average=avg(Amount)" --output:summary
datatable --action:addcolumn --name:lines --column:Total --value:=$row.Price$ * $row.Qty$
datatable --action:lookup --name:rates --column:Currency --value:$invoice.Currency$ --return:Rate --default:1 --output:rate
datatable --action:aggregate --name:sales --function:sum --column:Amount --output:totalSales
| Action | What it gives |
|---|---|
join |
Every row of the table with the matching rows of --with. --on names the column both tables share (CustomerId), or Left=Right when the names differ (Id=CustomerId); several pairs are separated by commas. --type:inner (default) keeps only rows with a match, left every row of the table (empty values where nothing matches), full also the rows of --with that matched nothing. The columns of --with are added; a name that is already taken gets a suffix, e.g. Name_2. |
distinct |
The table without duplicate rows; with --columns only those columns are compared (e.g. the e-mail address). The first row of each is kept. |
groupby |
One row per group of equal --columns values, with the calculations of --aggregates: Name=function(Column), comma separated. Functions: sum, count, avg, min, max, first, last; count() counts the rows of the group. In the editor, Calculations per group is a list: choose the calculation and the column, and the name of the result column. |
addcolumn |
A new column; with --value every row gets a value. A formula is calculated for each row, and $row.Column$ is the value of that row: --value:=$row.Price$ * $row.Qty$, --value:=if($row.Days$ > 30, "Late", "On time"). A value without = is the same for every row (it may still use $row.Column$). |
lookup |
The --return value of the first row whose --column equals --value (like Excel's VLOOKUP), or the whole row as a record when --return is empty. --default is the result when no row matches. |
aggregate |
One number from a column: --function:sum, count, avg, min or max. count without --column counts the rows. |
How values are compared: column names ignore case; numbers match numbers also when one is written as text (1 and "1.0"), but codes with leading zeros such as 007 stay text; text ignores case and spaces at the ends; empty values never match in a join. Empty cells are skipped by the calculations; sum and count of nothing are 0, avg, min and max of nothing are empty.
Tip:
$row$inaddcolumn --valueis the row being calculated, with the table's columns as fields (the formula builder lists them). If the task has its own variable calledrow, it keeps its value.
In the editor, column fields list the columns the table is known to have — from Create table, the Extract data wizard, or an earlier join or group by — and any other name can be typed. These actions need an agent from this version; an older agent is refused the task with a message asking to update it.
Excel workbooks
The excel command opens a workbook under a session name, and every following excel block (and loop --type:excelrows) uses that name:
excel --action:open --file:"C:\Reports\Sales.xlsx" --session:Sales
excel --action:readrange --session:Sales --output:sales
excel --action:writerange --session:Sales --range:"H1" --data:$summary$
excel --action:close --session:Sales --save:true
readrange gives a data table whose columns are the first row (with --header:false, a list of rows). writerange writes a data table as its column names and its rows, so readrange → datatable blocks → writerange round-trips a sheet.
There are two ways to open a workbook (--engine on open):
| Engine | What it does |
|---|---|
file (default) |
Reads and writes the .xlsx file directly. Excel does not need to be installed, nothing is shown, and it is the fastest. |
excel |
Works through Microsoft Excel on the agent machine: any file Excel opens (.xlsx, .xlsm, .xls, .xlsb, .csv), formulas calculated by Excel, and four more actions: runmacro, refreshall, exportpdf and autofit. --visible:false works without showing the Excel window. |
excel --action:open --file:"C:\Reports\Sales.xlsm" --session:Sales --engine:excel --visible:false
excel --action:refreshall --session:Sales
excel --action:runmacro --session:Sales --macro:"Module1.FormatReport" --args:"2026, 10"
excel --action:autofit --session:Sales --range:"A:F"
excel --action:exportpdf --session:Sales --file:"C:\Reports\Sales.pdf" --scope:workbook
excel --action:close --session:Sales --save:true
runmacroruns a VBA macro of the workbook (Module1.FormatReportor justFormatReport);--argspasses values to it (a list variable or comma-separated values) and--outputreceives what aFunctionreturns.refreshallrefreshes every data connection, query and pivot table and waits until Excel has finished.exportpdfsaves the active sheet (--scope:sheet, the default) or every sheet (--scope:workbook) as a PDF.autofitmakes the columns as wide as their contents.
Botream starts its own Excel for each session, turns off Excel's questions (such as "Update links?"), and always closes it again — after close, when the task ends and when it fails — so no Excel is left running in the background. Changes are only saved with save, saveas or close --save:true.
Note: The
excelengine needs Microsoft Excel installed on the agent machine and the agent running in a signed-in Windows session. A task with--engine:excelneeds an agent from this version. Macros run only when Excel allows them for the file (Trust Center).
Task inputs and outputs
A task can take values from its caller and give values back. Declare them at the top of the task with input and output:
input --name:invoiceNo --type:string
input --name:maxRetries --type:number --default:3 --required:false
output --name:bookingNo --type:string
--required(defaulttrue): the run fails with "Required input … was not provided" when the caller gives no value and there is no--default.--defaultis written likeset --value.- Give an output its value like any other variable, for example
set --name:bookingNo --value:$result.Number$.
When you start the task from the editor, the run dialog asks for the inputs under INPUTS, filled in with their defaults.
Calling another task
runtask calls a task by name (or by folder path, such as Finance/Invoices/Import, when several folders have a task of that name). Pass inputs as --<input>:value and receive outputs as --<output>To:variable:
runtask --name:BookInvoice --invoiceNo:$row.InvoiceNo$ --maxRetries:5 --bookingNoTo:booking
log --message:"Booked as $booking$"
The called task runs with its own variables; only the mapped outputs come back. In the properties panel, Choose task picks the task and Mappings lists its inputs and outputs; Auto map fills empty mappings from same-named variables. An error in the called task fails the runtask command, with its type kept (see Error handling).
System variables
Every task has the built-in $System$ without declaring it, like the system variables of other RPA tools. Its fields are read when they are used, so $System.Date$ is always the current time.
log --message:"Started on $System.Machine$ as $System.UserName$ at $System.Hour$:$System.Minute$"
file --action:copy --source:C:\In\report.xlsx --destination:"$System.DesktopFolder$\report-$System.Year$-$System.Month$.xlsx"
| Field | Type | Value |
|---|---|---|
$System.Date$ |
datetime | Now: the current date and time |
$System.Year$, Month, Day |
number | The current date (month 1 to 12) |
$System.Hour$, Minute, Second, Millisecond |
number | The current time (hour 0 to 23) |
$System.DayOfWeek$ |
string | The day of the week in English, such as Monday |
$System.Machine$ |
string | The name of the computer the task runs on |
$System.UserName$ |
string | The Windows user the agent runs as |
$System.Domain$ |
string | The Windows domain (or the computer name when there is none) |
$System.OSName$ |
string | The Windows edition and version, such as Windows 11 Pro 24H2 |
$System.CPUUsage$ |
number | Processor use of the whole computer, in percent (measured over a quarter of a second) |
$System.RAMUsage$ |
number | Memory in use, in percent |
$System.TotalRAM$, AvailableRAM |
number | Memory of the computer and free memory, in MB |
$System.Clipboard$ |
string | The text on the clipboard |
$System.TempFolder$, DesktopFolder, DocumentsFolder, DownloadsFolder |
string | Folders of the agent user |
$System.TaskName$ |
string | The name of the running task (a called task sees its own name) |
$System.ExecutionId$ |
string | The id of the run, as in the execution history |
$System.AgentName$ |
string | The name of the agent running the task |
$System.NewLine$, Tab |
string | A line break and a tab character |
$System$ is read-only, and no variable may be named System. The editor offers its fields after $System..
Global values
Values that many tasks share — a folder, a web address, a limit, a list of recipients — are kept once on the Global values page (Manage global values permission, Developer by default) and read in any task as $Global.Name$:
loop --type:eachfile --folder:$Global.InvoiceFolder$ --filter:*.pdf --variable:file
if --condition:"$file.Size$ > $Global.MaxFileSize$"
mail --action:send --to:$Global.SupportMail$ --subject:"Invoice too large: $file.Name$"
endif
endloop
- Each value has a name, a type (string, number, boolean, datetime, list, dictionary, record or data table), the value and a description. The value is entered with the same editors as a variable's initial value.
- A run gets the values of the moment it starts; a change applies to the next runs, without editing or pushing any task.
$Global$is read-only in a task (set --name:Globalis refused) and no variable may be namedGlobal. The editor offers the names after$Global..- Creating, changing and deleting global values is recorded in the audit log.
Note: Global values are not secret: everyone with Manage global values sees them, and they may appear in logs. Keep passwords and keys in vaults.
The error of a catch
There is no built-in error variable: a catch names the variable that receives the error, such as catch --error:err, and then $err.Message$, $err.Type$, $err.CommandLine$, $err.CommandType$ and $err.Timestamp$ describe it. See Error handling and retries.
Inspecting variables
- While debugging, the VARIABLES pane shows every variable when the run stops.
- When a run fails, the execution page shows the variables at the moment of the failure. Passwords are masked in both, and long values are shortened.