Skip to content
ZILLEALI edited this page Jun 19, 2026 · 1 revision

Writing Queries

comm($com, $arr) builds RouterOS API words from the $arr you pass it, and it decides how to format each word by looking at the first character of the array key. This is the single most common point of confusion for people new to the RouterOS API, plain or not, so this page covers it in isolation.

The three prefixes

Key prefix Resulting word Meaning
none =key=value Set a parameter, used for add, set, and most print options
? key=value (the ? you typed is preserved as-is) Filter rows server side, equivalent to a where clause
~ key~value Filter rows by regular expression match

The important detail: for ? and ~, you must include the prefix character in the array key string itself. The class does not add it for you, it only inspects the first character to decide which branch of formatting to use, then uses the key exactly as you wrote it.

// WRONG: comm() will treat "active" as a plain parameter, not a filter
$API->comm('/interface/print', array('active' => 'true'));

// RIGHT: the "?" is part of the key
$API->comm('/interface/print', array('?active' => 'true'));

Plain parameters

No prefix, used whenever you are setting a value rather than filtering:

$API->comm('/ip/address/add', array(
    'address'   => '10.10.10.1/24',
    'interface' => 'ether2',
));

This sends =address=10.10.10.1/24 and =interface=ether2.

Filtering with ?

Use this for server side filtering on print (and similar) commands, so the router only sends back rows you actually need instead of everything followed by a PHP-side foreach filter:

$disabled = $API->comm('/ip/firewall/filter/print', array(
    '?disabled' => 'true',
));

You can pass multiple ? filters in the same call, RouterOS treats them as AND conditions unless you explicitly send an ?#| (OR) operator word, which is outside the scope of what most scripts need.

Regex matching with ~

Use this when you need a pattern rather than an exact match, for example matching a subnet by prefix:

$matching = $API->comm('/ip/dhcp-server/lease/print', array(
    'count-only'       => '',
    '~active-address'  => '1.1.',
));

This counts only the leases whose active-address contains the substring 1.1. (RouterOS regex matching, not PHP PCRE, keep patterns simple, anchors and basic character classes work, lookaheads do not).

Removing and updating by .id

add and set/remove are not symmetric in what they expect. add takes plain parameters as shown above. set and remove need the internal .id of the row you are targeting, which you get from a prior print:

// Step 1: find the row
$hosts = $API->comm('/ip/dns/static/print', array(
    '?name' => 'router.local',
));

// Step 2: remove it using its internal id
if (!empty($hosts)) {
    $API->comm('/ip/dns/static/remove', array(
        '.id' => $hosts[0]['.id'],
    ));
}

Note .id has no =, ?, or ~ prefix character as its own key name, it starts with a literal dot, RouterOS internal property names always do, and comm() treats it like any other plain parameter since . is not one of the three special prefix characters.

A known rough edge in comm()

Issue #72 on this repository raised concerns about the loop that builds and sends each word in comm(), specifically whether the "is this the last word" detection could get out of sync and leave the connection waiting on data that never arrives. Reviewing the current comm() source directly: the last-word check ($i++ == $count - 1) evaluates the comparison using the pre-increment value of $i, which is functionally identical to comparing first and incrementing afterward, so the logic as it stands today is internally consistent. If you do encounter a hang, the more likely causes are:

  • Mixing manual write() calls with comm() on the same connection (they share no state about "is this the last word", so interleaving them confuses the sentence boundary)
  • A $timeout too short for a slow command or a busy router, raise $API->timeout before connecting
  • A firewall or NAT device silently dropping the connection mid-read rather than closing it, which looks identical to a router that never responds

If you think you have found a genuine reproduction of the issue, the most useful thing you can add to that GitHub issue is a minimal script plus the exact RouterOS version, since the original report did not include one.


Documentation contributed by Zill E Ali, Network Engineer and creator of mikrotik-laravel | GitHub | LinkedIn

Clone this wiki locally