Scripting Language Reference
Scripting Language Reference
Section titled “Scripting Language Reference”This page covers the RouterOS scripting language in depth — type system, variable scope, control flow, arrays, string functions, and practical patterns. For script management (creating, running, scheduling), see Scripting.
Variable Types and Scope
Section titled “Variable Types and Scope”RouterOS scripting is dynamically typed. The language automatically determines a value’s type based on context, and values can be coerced between types by operators.
Built-in Types
Section titled “Built-in Types”| Type | Description | Example literal |
|---|---|---|
num | Integer | 42, -5 |
str | String | "hello" |
bool | Boolean | true, false |
time | Duration/timestamp | 1h30m, 00:30:00 |
ip | IPv4 address | 192.168.1.1 |
ip-prefix | IPv4 prefix | 192.168.0.0/24 |
ip6 | IPv6 address | 2001:db8::1 |
ip6-prefix | IPv6 prefix | 2001:db8::/32 |
array | Ordered list or key-value map | {"a";"b";"c"} |
nil | No value / undefined | — |
id | Internal router object ID | — |
Use :typeof to inspect the type of any value:
:local x 42:put [:typeof $x] # num
:local s "hello":put [:typeof $s] # str
:local u:put [:typeof $u] # nilVariable Scope
Section titled “Variable Scope”Local Variables
Section titled “Local Variables”Declared with :local. A local variable is visible only within the current script block. It cannot be read from another script or from a nested scope that was not directly given the value.
:local count 0:local iface "ether1":local enabled trueGlobal Variables
Section titled “Global Variables”Declared with :global. Global variables persist across scripts and terminal sessions for the lifetime of the router session. Another script can read or modify them after they are declared.
# Script A — set a global:global maintenanceMode true
# Script B — read the global:global maintenanceMode:if ($maintenanceMode = true) do={ :log info "In maintenance mode"}A global variable must be declared with :global in each script that uses it. The declaration does not reset the value — it makes the existing global visible in scope.
Assignment
Section titled “Assignment”Assign or update a value with :set:
:local x 10:set x ($x + 5) # x = 15:set x ($x * 2) # x = 30To unset a variable (make it nil), call :set with no value:
:set x # x is now nilNil Checks
Section titled “Nil Checks”Always check for nil before using a variable that might be unset:
:local val:if ([:typeof $val] = "nil") do={ :set val "default"}:put $val # "default"Conditionals
Section titled “Conditionals”Basic :if
Section titled “Basic :if”:local load [/system resource get cpu-load]
:if ($load > 80) do={ :log warning ("High CPU: " . $load . "%")}:if with :else
Section titled “:if with :else”The :else block runs when the condition is false:
:local status [/interface get ether1 running]
:if ($status = true) do={ :log info "ether1 is up"} else={ :log error "ether1 is down"}Comparison Operators
Section titled “Comparison Operators”| Operator | Meaning |
|---|---|
= | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Boolean Operators
Section titled “Boolean Operators”Combine conditions with && (AND) and || (OR). Negate with !:
:local running [/interface get ether1 running]:local disabled [/interface get ether1 disabled]
# Both must be true:if ($running = true && $disabled = false) do={ :log info "ether1 is active"}
# Either condition triggers action:if ($running = false || $disabled = true) do={ :log warning "ether1 is not forwarding"}
# Negate a boolean:if (!$running) do={ :log error "Link is down"}Nested Conditionals
Section titled “Nested Conditionals”:local load [/system resource get cpu-load]:local mem [/system resource get free-memory]
:if ($load > 90) do={ :if ($mem < 10000000) do={ :log error "Critical: high CPU and low memory" } else={ :log warning "High CPU load" }} else={ :log info "System resources OK"}:while Loop
Section titled “:while Loop”Repeats a block while the condition holds:
:local retries 0
:while ($retries < 5) do={ :set retries ($retries + 1) :log info ("Attempt: " . $retries) :delay 2s}Always ensure the loop variable changes each iteration to avoid infinite loops.
:for Loop
Section titled “:for Loop”Iterates over a numeric range. The step parameter is optional (defaults to 1):
# Count from 1 to 5:for i from=1 to=5 do={ :put ("i = " . $i)}
# Count by twos:for i from=0 to=10 step=2 do={ :put ("even: " . $i)}
# Count down:for i from=5 to=1 step=-1 do={ :put ("countdown: " . $i)}:foreach Loop
Section titled “:foreach Loop”Iterates over arrays and lists:
:local servers {"10.0.0.1";"10.0.0.2";"10.0.0.3"}
:foreach srv in=$servers do={ :put ("Checking: " . $srv)}Iterate over command output (returns a list of internal IDs):
:foreach iface in=[/interface find where type="ether"] do={ :local name [/interface get $iface name] :local up [/interface get $iface running] :log info ($name . " running=" . $up)}Loop Control
Section titled “Loop Control”RouterOS does not have break or continue. Use :if conditions to skip iterations or a :while with a flag to exit early:
# Skip using :if:foreach i in={1;2;3;4;5} do={ :if ($i != 3) do={ :put ("Processing: " . $i) }}
# Early exit pattern with a flag:local found false:local items {"a";"b";"target";"c"}
:foreach item in=$items do={ :if ($found = false) do={ :if ($item = "target") do={ :set found true :log info "Found target" } }}Arrays
Section titled “Arrays”Creating Arrays
Section titled “Creating Arrays”Arrays are enclosed in braces with elements separated by semicolons:
:local servers {"10.0.0.1";"10.0.0.2";"10.0.0.3"}:local ports {80;443;8080}:local mixed {"eth1";1;true}Accessing Elements
Section titled “Accessing Elements”Use the -> operator with a zero-based index:
:local dns {"8.8.8.8";"8.8.4.4";"1.1.1.1"}
:put ($dns->0) # "8.8.8.8":put ($dns->1) # "8.8.4.4":put ($dns->2) # "1.1.1.1"Get the length with :len:
:put [:len $dns] # 3Key-Value Maps
Section titled “Key-Value Maps”Create associative arrays (maps) using key=value pairs:
:local router {"name"="core-sw";"site"="dc1";"ip"="10.0.0.1"}
:put ($router->"name") # "core-sw":put ($router->"site") # "dc1":put ($router->"ip") # "10.0.0.1"Accessing a missing key returns nil. Always check with :typeof before using:
:local val ($router->"missing"):if ([:typeof $val] = "nil") do={ :put "Key not found"}Iterating Arrays
Section titled “Iterating Arrays”:local nameservers {"8.8.8.8";"8.8.4.4";"1.1.1.1"}
:foreach ns in=$nameservers do={ :put ("NS: " . $ns)}Building Arrays from Command Output
Section titled “Building Arrays from Command Output”Convert command output to an iterable array with :toarray:
:local ifaceIds [/interface find where type="ether"]
:foreach id in=$ifaceIds do={ :local name [/interface get $id name] :put $name}Checking Array Length Before Access
Section titled “Checking Array Length Before Access”:local items {"a";"b"}:if ([:len $items] > 0) do={ :put ($items->0)}String Operations
Section titled “String Operations”Concatenation
Section titled “Concatenation”Use . to join strings:
:local a "Router":local b "OS":put ($a . $b) # "RouterOS":put ("Version: " . $b) # "Version: OS"Variables and expressions can be mixed inline:
:local host [/system identity get name]:local load [/system resource get cpu-load]:log info ($host . " CPU=" . $load . "%")Length
Section titled “Length”:local s "MikroTik":put [:len $s] # 8Substring
Section titled “Substring”:pick extracts a substring. Start index is inclusive; end is exclusive:
:local s "RouterOS":put [:pick $s 0 6] # "Router" (chars 0-5):put [:pick $s 6] # "OS" (from index 6 to end):find returns the position of the first match, or nothing (nil) if not found:
:local s "RouterOS-7.12":put [:find $s "-"] # 8
# With start offset:put [:find $s "O" 4] # 7
# Check if found:local pos [:find $s "X"]:if ([:typeof $pos] = "nil") do={ :put "Not found"}Type Conversion
Section titled “Type Conversion”| Command | Description | Example |
|---|---|---|
:tostr | Convert to string | [:tostr 42] → "42" |
:tonum | Convert to number | [:tonum "42"] → 42 |
:tobool | Convert to boolean | [:tobool "true"] → true |
:toip | Convert to IP type | [:toip "192.168.1.1"] |
:totime | Convert to time | [:totime "1h30m"] |
:toarray | Convert to array | [:toarray "a,b,c"] |
:typeof | Return type name | [:typeof $x] → "num" |
Running Scripts
Section titled “Running Scripts”From the CLI
Section titled “From the CLI”/system script run my-scriptWith Arguments
Section titled “With Arguments”Pass positional arguments when running a script:
/system script run testargs 10 20Inside the script, access them as $1, $2, etc.:
# Script: testargs:put ("arg1=" . $1 . " arg2=" . $2)Named arguments can also be passed and accessed as variables:
/system script run notify host=10.0.0.1 msg="link-down"# Script: notify:put ("host=" . $host . " msg=" . $msg)From the Scheduler
Section titled “From the Scheduler”Create a scheduler entry that calls an existing script:
/system scheduler add \ name=hourly-check \ start-time=startup \ interval=1h \ on-event=check-wan \ policy=read,write,testOr embed the script code directly in on-event:
/system scheduler add \ name=log-load \ interval=5m \ on-event=":log info ([/system resource get cpu-load] . \"% CPU\")" \ policy=read,testThe scheduler’s policy must be a superset of the permissions required by the commands in the script.
Asynchronous Execution
Section titled “Asynchronous Execution”Use :execute to run a script or command in a background job, non-blocking:
:execute "/system script run backup-upload"The caller does not wait for completion and does not receive output.
Error Handling
Section titled “Error Handling”:do / on-error
Section titled “:do / on-error”Wrap commands in a :do block to catch failures:
:do { /system backup save name=auto-backup :log info "Backup saved"} on-error={ :log error "Backup failed"}:onerror
Section titled “:onerror”:onerror runs a handler block and captures the error message:
:onerror e in={ /ip address add address=192.0.2.10/24 interface=ether1} do={ :log error ("Address add failed: " . $e)}:retry
Section titled “:retry”Retry a failing command with a delay and maximum attempt count:
:onerror e in={ :retry command={ /tool fetch url="https://example.com/config.rsc" dst-path="config.rsc" } delay=5s max=3} do={ :log error ("Fetch failed after retries: " . $e)}Common Patterns
Section titled “Common Patterns”Check Interface State and Alert
Section titled “Check Interface State and Alert”:local iface "ether1":local up [/interface get $iface running]
:if (!$up) do={ :log warning ($iface . " is DOWN") /tool e-mail send \ subject=("Link down: " . $iface) \ body=("Interface " . $iface . " lost link")}Iterate Interfaces and Log Status
Section titled “Iterate Interfaces and Log Status”:foreach id in=[/interface find] do={ :local name [/interface get $id name] :local state [/interface get $id running] :log info ($name . ": running=" . $state)}Retry Until Success
Section titled “Retry Until Success”:local ok false:local attempts 0
:while (!$ok && $attempts < 10) do={ :set attempts ($attempts + 1) :do { /tool ping 8.8.8.8 count=1 :set ok true } on-error={} :if (!$ok) do={ :delay 5s }}
:if ($ok) do={ :log info "Connectivity restored after " . $attempts . " attempts"} else={ :log error "No connectivity after " . $attempts . " attempts"}Global State Flag Between Scripts
Section titled “Global State Flag Between Scripts”# Script: set-maintenance:global maintenance true
# Script: check-maintenance:global maintenance:if ([:typeof $maintenance] = "nil") do={ :set maintenance false }:if ($maintenance) do={ :log info "Skipping — maintenance mode"} else={ # ... do work}Build a Summary String
Section titled “Build a Summary String”:local id [/system identity get name]:local ver [/system package get routeros version]:local uptime [/system resource get uptime]:local cpu [/system resource get cpu-load]:local freemem [/system resource get free-memory]
:local summary ($id . " v" . $ver . " up=" . $uptime . " cpu=" . $cpu . "% free=" . $freemem):log info $summary