Skip to content

Bulk Operations

The Bulk Operations page (also called Change Bulk in some menus) lets you apply one action to many subscribers in a single call. It is the right tool when you need to renew 5,000 customers at the start of the month, disconnect everyone on a plan you’re retiring, push a price change to a reseller’s whole book, or hand a sub-reseller’s subscribers off to a different reseller.

Every action runs server-side in a single transaction-per-row loop so that one bad subscriber doesn’t fail the batch. The result message tells you how many succeeded and how many were skipped. A single audit log row records the whole bulk action — not one row per subscriber.

  • SidebarChange Bulk (lightning-bolt icon).
  • Direct URL: /change-bulk.

Requires subscribers.change_bulk. Each specific action also requires its own permission — bulk renew needs subscribers.renew, bulk reset-FUP needs subscribers.reset_fup, etc. Admins bypass all checks.

Two-column layout:

ColumnPurpose
Left (2/3)Filter builder. Stack filters until you’ve narrowed to the subscribers you want. Sticky pagination at the bottom shows you the matching count.
Right (1/3)Action panel. Pick the action, fill in the action value if required, click Apply. Confirmation modal with the count before the request fires.

The right column is sticky — it stays in view as you scroll through matches on the left.

Stackable filter pills. Each pill is an AND against the others. Available filters:

FilterNotes
Service planOne or more plans.
StatusActive / inactive / expired / expiring / online / offline.
ResellerOne or more resellers; toggle to include sub-resellers.
FUP level0 (none), 1/2/3 (daily), 4/5/6 (monthly).
Expiry rangeexpiry_date BETWEEN x AND y.
Created rangecreated_at BETWEEN x AND y.
Last seen rangeWhen the subscriber was last online.
Region / Country / CityGeographic.
Payment methodcash, card, etc.
SearchFree text across username, name, phone, email, address.

The match-count badge updates as you add filters. When it shows the intended count, the action becomes “safe to fire”.

Selected action determines the right-hand panel. Each action calls the BulkAction handler with { ids, action, action_value }. The full list:

ActionWhat it doesPermissionAction value
renewExtend expiry_date by N days (default 30), reset daily FUP, charge reseller balance per subscriber.subscribers.renewdays
disconnectCoA disconnect via MikroTik API pool (audit perf fix v1.0.546).subscribers.disconnect
enable / disable (set_active/set_inactive)Toggle subscriber status.subscribers.inactivate
reset_fupZero daily/monthly FUP counters, restore service-default rate in radreply.subscribers.reset_fup
reset_monthly_fupZero only monthly counters.subscribers.reset_fup
reset_all_countersDaily + monthly + CDN counters.subscribers.reset_fup
deleteSoft delete; remove radcheck/radreply.subscribers.delete
set_expirySet absolute expiry date. v1.0.551 bounds: within −1 / +10 years of now.subscribers.editdate
add_daysAdd N days to current expiry.subscribers.renewdays
set_serviceSwitch to a new service plan and update price.subscribers.change_serviceservice ID
set_resellerMove to a different reseller. v1.0.546–547 whitelist: a non-admin caller can only move subscribers to their own reseller or a direct child sub-reseller; security audit refused any other target.subscribers.edit + scope checkreseller ID
set_static_ipSet static_ip and write radreply Framed-IP-Address. v1.0.551 IPv4 validation — invalid strings (Unicode, partial CIDR, IPv6) refused before write. Empty string clears. Duplicate detection prevents two users with the same IP.subscribers.editIP
set_monthly_quotaSet monthly_quota in bytes. v1.0.551 bounds: 0–100 TB (102,400 GB).subscribers.editGB
set_daily_quotaSet daily_quota in bytes. v1.0.551 bounds: 0–10 TB (10,485,760 MB).subscribers.editMB
set_priceSet per-subscriber price override. v1.0.551 bounds: 0–1,000,000.subscribers.editamount
set_passwordHash with bcrypt, update radcheck Cleartext-Password.subscribers.editpassword
reset_macClear MAC and radcheck Calling-Station-Id.subscribers.reset_mac
set_nasAssign to a different NAS.subscribers.editNAS ID
refillAdd quota from prepaid card.subscribers.refill_quotacode
renameChange username across subscribers, radcheck, radreply, radacct.subscribers.renamenew username

The v1.0.551 sanity bounds on quota, price, expiry date, and static IP came out of the 2026-05-12 security audit; each refusal is logged with SECURITY: BulkAction <action> refused — ... so you can detect a fat-finger or a hostile reseller.

POST /api/subscribers/bulk-action body:

{
"ids": [123, 456, 789, ...],
"action": "renew",
"action_value": "30"
}

The handler:

  1. Permission gate: refuses unauthorised callers based on the action.
  2. Scope: a reseller without subscribers.view_all is restricted to their own subscribers (and sub-resellers’ subscribers) before the loop begins — even if a malicious client sends IDs outside that scope.
  3. Pre-loads the caller’s reseller once (avoids N+1 balance lookups during renew).
  4. Pre-loads every NAS used by the selected subscribers (avoids N+1 queries for disconnect / CoA paths).
  5. Loops the selected IDs; for each one, applies the action and increments success or failed counters.
  6. Records a single audit log row: ChangeBulk: <ActionName> for <success> subscribers (<failed> failed).
  7. Returns { success, failed, total }.

The disconnect path uses the MikroTik connection pool rather than mikrotik.NewClient — at ~10 ms per fresh TCP+login handshake, a 1,000-subscriber disconnect without pooling adds ~10 seconds of wall-clock latency (audit 2026-05-11 backend perf HIGH). Sibling paths at lines ~2488 and ~2523 already use the pool; the bulk path was patched to match.

  • The subscriber was deleted between filter time and action time.
  • For renew: the caller is a reseller and their balance is below the subscriber’s price.
  • For set_static_ip: another user already holds that IP in radreply.
  • For set_service: the service ID doesn’t exist.
  • For set_reseller: the target reseller isn’t allowed by the v1.0.547 whitelist.
  • For set_*_quota / set_price / set_expiry: the value violated the v1.0.551 sanity bounds.

Failure counts are reported in the response. The audit row’s description carries both counts so you can drill into “which 3 failed?” later by filtering the audit log.

Renew everyone whose plan is “10M Family” at the start of the month

Section titled “Renew everyone whose plan is “10M Family” at the start of the month”
  1. Filter: service = 10M Family, status = active.
  2. Confirm the count matches your billing system’s expectation.
  3. Action = Renew, days = 30.
  4. Confirm the modal. Wait for the result toast.
  5. If failures > 0, open Audit Log, find the bulk row, drill into the subscribers’ detail pages to see why each failed.

Move 200 subscribers from one sub-reseller to another

Section titled “Move 200 subscribers from one sub-reseller to another”
  1. Filter: reseller = the source sub-reseller.
  2. Action = Set reseller, target = the destination sub-reseller. (Permission gate: the destination must be your own reseller or a direct child of yours; admins bypass.)
  3. Confirm. The 200 subscribers’ reseller_id flips in a single batch.
  4. The audit log shows you (the actor), the source, the destination, and the count.

Apply a price increase to a reseller’s whole book

Section titled “Apply a price increase to a reseller’s whole book”
  1. Filter: reseller = the affected reseller.
  2. Action = Set price, value = the new amount.
  3. Confirm. Every selected subscriber’s price is updated; the next renewal will charge the new amount.
  4. Optional: trigger Communication Rules to notify each subscriber of the change.
PermissionWhat it gates
subscribers.change_bulkOpen the page and submit bulk requests.
Per-action permissionsEach action also requires its own permission as shown in the action table.
subscribers.view_allWithout it, a reseller is restricted to their own subscribers and sub-resellers’ subscribers.
  • Subscribers — the list view where you select rows and trigger the same actions per-row.
  • Audit Logs — every bulk action writes one audit row with success/failure counts.
  • Users & Permissions — configure which resellers can do which bulk action.
  • Reports — confirm the impact of a bulk action after the fact.
  • Billing & Invoices — bulk renew generates invoices and transactions.