Launch announcement
NinjaOne Tracker ships today — per-ticket status timers for NinjaOne RMM queues, no extra tab.
Today is the day — Extensify ships NinjaOne Tracker: a Manifest V3 Chrome extension that drops a bottom-right overlay onto the NinjaOne RMM ticket queue with live mm:ss counters on every visible ticket row, a "stuck" highlight past the fifteen-minute mark, and a Pro plan unlocked via ExtensionPay. No new tab opened, no extra dashboard to load.
Per-row mm:ss timers
A live counter pinned to every ticket row — no need to open the ticket to see how long it has been sitting.
Stuck at >15 min
Any row past the fifteen-minute mark is highlighted as "stuck", with a queue-wide count in the overlay header line.
Free→Pro via ExtensionPay
Upgrade in one click — ExtensionPay handles the checkout, the extension caches the Pro flag locally, the dashboard stamps the cookie server-side.
What ships today
Per-ticket mm:ss counters — on the NinjaOne board you already work in.
NinjaOne Tracker injects a fixed bottom-right overlay into the NinjaOne RMM ticket queue. Each visible ticket row carries its own mm:ss counter that ticks every second: a "Pro only" badge flags columns unlocked for paying users, a header line summarizes queue depth and aged-ticket count, and any row older than fifteen minutes is highlighted as "stuck" — no separate tab, no popup roulette, no break in your queue. The extension runs entirely in the browser via a Manifest V3 content script scoped by host_permissions to https://app.ninjaone.com/* and https://*.ninjarmm.com/*.
Who it's for
MSPs and IT teams running NinjaOne RMM — operators who live in a service queue.
If the NinjaOne RMM dashboard is the tab you keep open all day, this extension is built for you. Operators scanning an ageing queue for stuck items, leads watching SLA-bound escalations drift toward the red, and dispatchers triaging who-picks-up-next all want the same thing: a glanceable indicator pinned to the row, not another dashboard. The /supported-sites page lists the platforms we ship on, and /#pricing covers the Free-vs-Pro split for this one.
Permissions, briefly
storage + activeTab + the two NinjaOne hosts — that is the whole list.
The extension requests just two runtime permissions: storage (to cache the Pro flag and the ExtensionPay installation id locally) and activeTab (to talk to the active NinjaOne RMM tab). host_permissions scopes the content script to the two NinjaOne domains above — there is no broad-host permission, no telemetry endpoint, no background network call, and no scrapping of the NinjaOne API. License enforcement lives server-side on /api/entitlement; the local pro flag is convenience, not the security boundary.
Pro activation via ExtensionPay
How the Free→Pro flip actually happens, end to end.
Open the popup and click Upgrade: the popup asks the background service worker for paid status via GET_PAID_STATUS. On an unpaid install, the popup renders an "Upgrade" button that opens ExtensionPay's hosted checkout in a new tab — the background service worker loads the SDK via importScripts("https://extensionpay.com/extpay.js"). On payment success, ExtensionPay fires an extpay:paid message; the background flips chrome.storage.local.pro to true and persists the installation id. The next time the popup opens, it also POSTs the extpayInstallationId to ${APP_URL}/api/license/activate, where the server stamps the HMAC-signed ninjaone_ent HttpOnly cookie used by the dashboard. The content script then re-reads the cached pro flag and re-renders the overlay in Pro mode on the next queue load.
Supported NinjaOne versions
Today: app.ninjaone.com and the legacy *.ninjarmm.com domain.
The v0.3.0 manifest pins host_permissions to https://app.ninjaone.com/* and https://*.ninjarmm.com/* — i.e. NinjaOne RMM, plus the legacy ninjarmm domain for tenants that have not migrated. The content script re-scans on SPA route changes inside that scope without double-injecting itself; screens outside those two hosts are left alone. What we ship next lives on /supported-sites and the pricing page — these are not in scope for this announcement.
Pro activation walk-through
The Free→Pro flip, step by step.
- 1
Popup checks paid status (GET_PAID_STATUS)
The popup asks the background service worker for current paid state. On an unpaid install it renders the "Upgrade" button.
- 2
Background loads ExtensionPay & opens checkout
importScripts("https://extensionpay.com/extpay.js") is invoked from the service worker; the popup button opens ExtensionPay's hosted checkout in a new tab.
- 3
extpay:paid message flips chrome.storage.local.pro
On payment success the background service worker caches
pro: trueand the installation id locally. Free→Pro state flips without a page reload. - 4
Popup POSTs extpayInstallationId to /api/license/activate
On the next popup open the extension claims its installation id; the server stamps an HMAC-signed
ninjaone_entHttpOnly cookie used by the dashboard. - 5
Content script re-renders the overlay in Pro mode
The content script reads the cached
proflag and renders the extended overlay (additional columns, queue stats) on the next page load. The claim POST is idempotent — re-runs never consume a seat.
FAQ
Questions about the launch.
Install paths, the ExtensionPay upgrade flow, and the NinjaOne versions we ship on.
How do I install NinjaOne Tracker?
Today the build is shipped unpacked: open chrome://extensions, toggle Developer mode on, click "Load unpacked", and pick the extension/ directory. Pin the extension from the puzzle menu so it stays visible next to your address bar. The Chrome Web Store one-click install is the upcoming v0.x publish path — drop your email into the notify form on the connectwise launch post (or check back here) to be told the day it goes live. For staging deploys, override <meta name="app-url"> in extension/popup.html so the popup claims against the staging /api/license/activate route.
How does the Pro upgrade flow work via ExtensionPay?
Open the popup and click Upgrade. The popup asks the background service worker for paid status via GET_PAID_STATUS. On an unpaid install the popup renders an Upgrade button that opens ExtensionPay's hosted checkout in a new tab — the background service worker loads the SDK with importScripts("https://extensionpay.com/extpay.js"). On payment success, ExtensionPay fires an extpay:paid message; the background flips chrome.storage.local.pro to true and persists the installation id. The next time the popup opens, it also POSTs the extpayInstallationId to ${APP_URL}/api/license/activate, where the server stamps the HMAC-signed ninjaone_ent HttpOnly cookie used by the dashboard. The content script re-reads the cached pro flag and re-renders the overlay in Pro mode on the next queue load.
Which NinjaOne versions does the extension support?
NinjaOne RMM on app.ninjaone.com and the legacy *.ninjarmm.com domain — those are the host_permissions pinned in the v0.3.0 manifest. The content script is scoped to those two hosts only and re-scans on SPA route changes without double-injecting. Tenants on the legacy ninjarmm.com domain are supported until they migrate; tenants on any NinjaOne endpoint outside those two hosts fall back to the un-timed queue view.
What permissions does the extension request, and why?
Two runtime permissions: storage (to cache the Pro flag and the ExtensionPay installation id locally) and activeTab (to talk to the active NinjaOne RMM tab). host_permissions scopes the content script to https://app.ninjaone.com/* and https://*.ninjarmm.com/* — no broad-host permission, no analytics endpoint, no telemetry, no NinjaOne API call. License enforcement is server-side on /api/entitlement; the local pro flag is convenience, not the security boundary.
Is my ticket data sent anywhere?
No. The extension reads what is already rendered on the NinjaOne RMM dashboard DOM and never calls the NinjaOne API. The single outbound network request is the claim POST to ${APP_URL}/api/license/activate, and its body carries only the buyer's ExtensionPay installation id — never ticket bodies, subjects, requester info, or notes.