Our scheduler guide covered basic scripts and scheduling. This guide goes deeper into the RouterOS scripting language itself – the syntax, data types, control flow, and patterns you need for serious automation.
Scripting Basics
Variables
# Local variable (scope: current script)
:local myVar "Hello"
:local count 42
:local isActive true
# Global variable (persists across scripts)
:global sharedVar "Available everywhere"
# Print
:put $myVar
:log info "Count is $count"
Data Types
:local myString "text" # String
:local myNumber 100 # Number
:local myBool true # Boolean
:local myIP 192.168.88.1 # IP address
:local myTime 01:30:00 # Time
:local myArray {1; 2; 3} # Array
:local nothing # Nothing (nil)
String Operations
:local first "Mikro"
:local second "Tik"
:local combined ("$first$second") # "MikroTik"
# String length
:local len [:len $combined] # 8
# Substring
:local sub [:pick $combined 0 5] # "Mikro"
# Find
:local pos [:find $combined "Tik"] # 5
# Convert
:local numStr [:tostr 42] # "42"
:local strNum [:tonum "42"] # 42
Control Flow
If/Else
:local cpu [/system resource get cpu-load]
:if ($cpu > 80) do={
:log warning "CPU high: $cpu%"
} else={
:log info "CPU normal: $cpu%"
}
For Loop
:for i from=1 to=10 do={
:put "Iteration $i"
}
Foreach
:foreach iface in=[/interface find type=ether] do={
:local name [/interface get $iface name]
:local running [/interface get $iface running]
:put "$name - running: $running"
}
While Loop
:local attempts 0
:while ($attempts < 5) do={
:set attempts ($attempts + 1)
:if ([/ping 8.8.8.8 count=1] > 0) do={
:log info "Internet OK on attempt $attempts"
:set attempts 5
} else={
:delay 5s
}
}
Arrays
# Create array
:local servers {"1.1.1.1"; "8.8.8.8"; "9.9.9.9"}
# Access element (0-indexed)
:put ($servers->0) # "1.1.1.1"
:put ($servers->2) # "9.9.9.9"
# Iterate
:foreach srv in=$servers do={
:if ([/ping $srv count=1] = 0) do={
:log error "DNS server $srv unreachable"
}
}
# Key-value array
:local config {"name"="Router1"; "location"="DC1"; "role"="core"}
:put ($config->"name") # "Router1"
Functions
# Define a function (stored as a global variable)
:global sendAlert do={
:local msg $1
:local botToken "YOUR_BOT_TOKEN"
:local chatId "YOUR_CHAT_ID"
/tool fetch url="https://api.telegram.org/bot$botToken/sendMessage\?chat_id=$chatId&text=$msg" keep-result=no
:log info "Alert sent: $msg"
}
# Call the function
$sendAlert "WAN link is down!"
Error Handling
# :do on-error catches errors
:do {
/ip address add address=192.168.1.1/24 interface=ether99
} on-error={
:log error "Failed to add IP – interface might not exist"
}
# Check before acting
:if ([:len [/interface find name=ether5]] > 0) do={
/interface set ether5 disabled=no
} else={
:log warning "ether5 not found"
}
Practical Scripts
Script 1: WAN Failover Monitor
# Check primary WAN, switch to backup if down
:local primaryGW 203.0.113.1
:local backupGW 198.51.100.1
:local pingResult [/ping $primaryGW count=3]
:if ($pingResult = 0) do={
:log warning "Primary WAN down – switching to backup"
/ip route set [find comment="primary-default"] disabled=yes
/ip route set [find comment="backup-default"] disabled=no
} else={
/ip route set [find comment="primary-default"] disabled=no
/ip route set [find comment="backup-default"] disabled=yes
}
Script 2: Auto-Block Heavy Users
# Find PPPoE users exceeding 100GB and add to address list
:foreach session in=[/ppp active find] do={
:local user [/ppp active get $session name]
:local bytes [/ppp active get $session bytes]
:local download [:pick $bytes ([:find $bytes "/"] + 1) [:len $bytes]]
:if ([:tonum $download] > 107374182400) do={
:local ip [/ppp active get $session address]
/ip firewall address-list add list=heavy-users address=$ip timeout=1d comment=$user
:log warning "Heavy user: $user ($ip) exceeded 100GB"
}
}
Script 3: Daily Health Report
:local identity [/system identity get name]
:local uptime [/system resource get uptime]
:local cpu [/system resource get cpu-load]
:local memFree [/system resource get free-memory]
:local memTotal [/system resource get total-memory]
:local activeUsers [:len [/ppp active find]]
:local version [/system resource get version]
:local report "\F0\9F\93\8A Daily Report: $identity\n"
:set report "$report Uptime: $uptime\n"
:set report "$report CPU: $cpu%\n"
:set report "$report RAM: $memFree / $memTotal\n"
:set report "$report Active users: $activeUsers\n"
:set report "$report Version: $version"
:log info $report
# Send via Telegram or email
Script 4: Bulk DNS Import
:local domains {"ads.google.com"; "doubleclick.net"; "tracking.example.com"; "analytics.spam.com"}
:foreach domain in=$domains do={
:do {
/ip dns static add name=$domain address=0.0.0.0 comment="Blocked"
:log info "Blocked: $domain"
} on-error={
:log warning "Already blocked: $domain"
}
}
Scheduling Scripts
# Run every 5 minutes
/system scheduler add name=wan-monitor interval=5m on-event="/system script run wan-failover"
# Run daily at 7 AM
/system scheduler add name=daily-report interval=1d start-time=07:00:00 on-event="/system script run health-report"
# Run once at specific date
/system scheduler add name=one-time start-date=2026-12-31 start-time=23:59:00 on-event=":log info \"Happy New Year\"" interval=0
See our scheduler guide for more scheduling patterns.
Environment Variables
# Available inside scheduled scripts:
:local scriptName $0 # Name of the current script
:local schedulerOwner $1 # Owner of the scheduler
# Get system info
:local hostname [/system identity get name]
:local date [/system clock get date]
:local time [/system clock get time]
HTTP Fetch (API Calls)
# GET request
/tool fetch url="https://api.example.com/status" output=user as-value
# POST request
/tool fetch url="https://hooks.slack.com/services/YOUR/WEBHOOK" http-method=post http-data="{\"text\":\"Alert from MikroTik\"}" http-header-field="Content-Type: application/json" output=none
# Download a file
/tool fetch url="https://your-server.com/blocklist.rsc" dst-path="blocklist.rsc"
Debugging Scripts
:put $variable– Print to terminal (only in terminal, not in scheduler).:log info $message– Write to system log (visible everywhere)./system script run scriptName– Run interactively and see output./system script print detail– Check for syntax errors (last-started, run-count)./log print where topics~"script"– Filter script-related log entries.
Best Practices
- Use :local, not :global – Global variables persist and can cause conflicts between scripts.
- Add error handling – Wrap risky operations in
:do { } on-error={ }. - Log extensively – Use
:log info/warning/errorfor debugging production scripts. - Test in terminal first – Run scripts manually before scheduling them.
- Keep scripts short – Break complex logic into multiple scripts that call each other.
- Comment your code – Use
# commentsfor future maintenance.
Conclusion
RouterOS scripting turns your MikroTik from a static configuration device into a programmable network appliance. Automated failover, health monitoring, bulk operations, webhook alerts – anything you do manually can be scripted and scheduled. Master the basics (variables, loops, error handling), then build up to production automation scripts.
For HTTP-based automation, also see our REST API guide. For managing users programmatically, MikroRadius provides its own API that complements RouterOS scripting.