NQL is the query language that powers custom investigations, dashboard widgets, and Flow workflow triggers in Nexthink Infinity. It reads like SQL but is purpose-built for the Nexthink data model: you query entities like devices, users, packages, and events rather than database tables. This guide covers what you need to write the queries that matter, not a complete language reference.
Before the Syntax
The fastest way to learn NQL is not to read the reference documentation first: it's to use Workspace. Ask Workspace a question in plain English, see the NQL it generates, run the query, then modify the NQL to understand what each part does. You'll learn more in 20 minutes of this pattern than in an hour of reading syntax definitions.
The Data Model
NQL queries run against entities: the core objects in the Nexthink data model. Understanding which entity to query determines what data you can access and how you can aggregate it. These are the entities you'll use in almost every investigation.
The primary entity. Represents an individual managed endpoint. Contains hardware specs, OS, performance metrics, DEX score, collector status, and device-level events.
deviceThe employee or user assigned to a device. Contains identity attributes from the directory: name, email, department, location, job title.
userSoftware installed on devices. Represents individual applications or packages with version, publisher, and installation status per device.
packageAn individual application execution event: when a specific application was launched on a specific device. Contains timing, performance data, and crash status.
executionNetwork connection events. Represents network activity from a device: connection quality, throughput, latency, and destination information.
connectionPlatform alerts triggered by threshold conditions or anomaly detection. Contains alert type, severity, affected device, and timestamp.
alertEngage campaign interactions: responses to surveys, notification acknowledgements, campaign delivery events.
campaign_eventFlow (formerly Remote Action) execution records. Contains workflow name, trigger, execution status, and output per device.
remote_actionGeneric endpoint events: BSODs, login events, policy changes, security detections. The most granular layer of Collector data.
eventCore Syntax
NQL follows a SELECT → FROM → WHERE → AGGREGATE structure similar to SQL. The key difference is that you're always selecting from an entity type, and your field names come from the Nexthink data schema for that entity.
select (device.name, device.os.version, device.dex.score) from device
select (device.name, device.os.version, device.storage.free.ratio) from device where device.storage.free.ratio < 0.10 -- less than 10% free disk
select (device.name, user.department, device.dex.score) from device where device.dex.score < 5.0 and user.department == "Finance"
select (count()) from device where device.collector.status == "online"
select (user.department, count()) from device where device.dex.score < 5.0 aggregate by user.department
select (user.department, avg(device.dex.score)) from device aggregate by user.department sort by avg(device.dex.score) asc -- worst first
select (device.name, count()) from execution where execution.status == "crashed" and execution.start > date() - days(7) -- last 7 days aggregate by device.name sort by count() desc limit 20
select (package.name, package.version, count()) from package where user.department == "Engineering" and package.publisher == "Microsoft" aggregate by (package.name, package.version) sort by count() desc
Investigation Patterns
These are the queries that come up most often in Nexthink investigations. Use them directly, or use them as starting points to modify for your environment. Every query here can be run in Workspace or pasted into a custom dashboard widget.
select (device.name, user.display.name, user.department, device.dex.score) from device where device.collector.status == "online" and device.dex.score < 5.0 sort by device.dex.score asc limit 50
select (user.department, count()) from device where device.collector.status == "offline" aggregate by user.department sort by count() desc
select (execution.application.name, count()) from execution where execution.status == "crashed" and execution.start > date() - days(30) aggregate by execution.application.name sort by count() desc limit 10
select (device.name, user.display.name, count()) from execution where execution.application.name == "Microsoft Teams" and execution.status == "crashed" and execution.start > date() - days(7) aggregate by (device.name, user.display.name) sort by count() desc
select (device.name, user.department, user.display.name) from device where device.os.type == "windows" and not exists ( select () from package where package.name == "CrowdStrike Falcon Sensor" ) sort by user.department asc
select (device.name, device.os.version, user.department) from device where device.os.type == "windows" and device.os.version != "Windows 11" sort by device.os.version asc
select (package.name, package.version, count()) from package where package.last.execution.date < date() - days(90) and package.publisher == "Adobe" -- target specific vendor aggregate by (package.name, package.version) sort by count() desc
select (device.name, package.name, package.version) from package where package.name like "%Microsoft 365%" sort by (device.name, package.version) asc
Writing Better Queries
Add device.collector.status == "online" to device queries unless you specifically want offline devices. Offline devices often have stale data that will skew your results: a device that went offline 3 months ago still shows its last-known DEX score.
Many entities accumulate historical data. Without a time filter, execution queries return all execution events ever. Always add a where execution.start > date() - days(N) filter. Without it, large environments will time out or return millions of rows.
When exploring data, add limit 100 to your queries while you're validating the logic. Remove the limit once you're confident the query is correct. This prevents accidentally querying the entire fleet and waiting a minute for 50,000 rows.
Workspace-generated NQL is correct but sometimes verbose. Use it as a starting point, then simplify: remove fields you don't need, tighten time windows, add aggregations. The generated query shows you the correct field names even if you rewrite the logic.
NQL field names are case-sensitive and must match the schema exactly. Run a simple SELECT with the fields you want before building a dashboard widget. A widget that fails to load because of a typo in a field name is frustrating to debug under time pressure.
Dashboard widgets that return 10,000 device rows are slow to load and useless to read. Design dashboard queries to return aggregated values, counts, averages, percentages,not row-level data. Row-level data belongs in on-demand investigations, not persistent widgets.
Continue
Workspace translates plain language into NQL and is the fastest way to explore the data model. The Use Cases guide shows how NQL-backed investigations combine with Flow and Engage to create complete DEX programs.
NQL syntax examples on this page reflect the Nexthink Infinity platform. Field names and syntax may evolve: consult the official Nexthink documentation for the current schema reference. View full references →