Skip to main content

Custom query

A custom query filters devices on their own values, for conditions the ordinary filters cannot express - every device reporting a temperature below 15, say. It is available wherever devices are filtered: the device list, the map, the views built on them, and reports, where it decides which IoT nodes the report covers.

Skip to Ready-made queries if you want something to copy and adapt.

Where to type one

In the device list and on the map

  1. Press Filter above the device list.
  2. In the panel that opens on the right, go to Custom query and type the query in the box.

Reaching the custom query box: the Filter button above the device list, and the filter panel it opens with the Custom query box at the bottom

The box carries an example as its placeholder, temperature<15, and a link to this page. The query applies as an active filter alongside the ordinary ones, so it can narrow a selection the other filters have already made.

In a report

A report uses the same query to decide which IoT nodes it covers, but it is written into the report definition rather than typed into a panel. Each source in the definition has a query field, and it takes exactly the syntax described on this page:

In a report definitionThe report covers
"query": "rssi"Every device reporting a signal strength
"query": "presence"Every device reporting presence
"query": "contextMap.building=='Building 2';temperature"Devices in one building that report a temperature

A single bare field name is the most common form, because a report usually wants every device that measures the thing being reported on. Anything else on this page works there too, which is how a report is narrowed to one building, one floor or one device model. See Reports for where the definition lives.

Operators

OperatorMatchesWorks onExample
==Exactly equaltext, numbers, dates, booleansname=='My Device'
:The same as ==text, numbers, dates, booleansname:'My Device'
~=Contains, anywhere in the valuetextname~=Strips
!=Not equaltext, numbers, dates, booleansname!='My Device'
>Greater thannumbers, datestemperature>20
>=Greater than or equalnumbers, datestemperature>=20
<Less thannumbers, datestemperature<20
<=Less than or equalnumbers, datestemperature<=20
!The field does not existany!temperature
(field alone)The field existsanytemperature

Case sensitivity

The two behave differently, which is worth knowing before a query comes back empty:

  • ~= ignores case. name~=strips finds "Strips-Guard" as well as "strips-guard".
  • == and != do not. name=='strips-guard' will not match "Strips-Guard".

So ~= is usually the friendlier operator for names typed by hand.

Combining conditions

CharacterMeaningExample
;AND - every condition must holdtemperature>25;humidity<40
|OR - either condition may holdtemperature>25|humidity<40
,A list of values for one fielddeviceModelName==strips,presence
.A nested fieldcontextMap.floor==3

Two things to watch:

  • The comma is not a general OR. It lists alternative values for the field in front of it. deviceModelName==strips,presence is right; temperature<15,humidity<50 is not, because those are two different fields. That one needs the pipe: temperature<15|humidity<50.
  • Use one OR group per query. A query can mix ; and one | group, as in deviceModelName==strips;temperature>25|humidity<40. A second OR group replaces the first rather than adding to it.

Text values

Wrap a value in single quotes to keep it a literal string: name=='Room 2'. This matters when the value could be read as something else - a value like 2026-09-01 becomes a date unless it is quoted, and 0102 becomes the number 102.

Quotes are required when the value contains a space.

Searching on dates

Any value written as an ISO 8601 date is treated as a date by every operator except ~=. That makes the comparison operators work on time:

QueryFinds
reportedAt>2026-09-01Devices that have reported since 1 September 2026
reportedAt<2026-09-01Devices whose last report was before that date
reportedAt>=2026-09-01T08:00:00ZFrom a particular time, UTC
reportedAt>2026-09-01;reportedAt<2026-09-08Reported during that week - a range is two conditions, not one
!reportedAtDevices that have never reported at all

Accepted forms are ordinary ISO 8601: 2026-09-01, 2026-09-01T08:00:00Z, 2026-09-01T08:00:00.000Z. A date on its own is midnight at the start of that day.

Three things to know:

  • A range takes two conditions joined by ;, one for each end.
  • ~= does not work on dates. It keeps a date-like value as literal text, so it matches the characters rather than the moment.
  • A bare year is a number, not a date. reportedAt>2026 compares against the number 2026 and finds nothing. Write reportedAt>2026-01-01.

Dates are absolute, so a saved view containing one keeps pointing at the same moment as time passes. For devices that have gone quiet, a rolling window is usually what you want instead: the device list's Reported within filter, or the map's Last seen section.

Ready-made queries

Copy one and change the field or the value.

Finding devices

QueryFinds
name~=stripsEvery device with "strips" in its name, in any capitalisation
name~='Room 2'Names containing "Room 2"
deviceModelName==strips-guardOne hardware model
deviceModelName==strips-guard,strips-dripEither of two models
!nameDevices with no name set
devEui~=0102A partial DevEUI, useful from a label

Readings and thresholds

QueryFinds
temperature>25Too warm
temperature<15|humidity>70Too cold or too damp
temperature>=18;temperature<=24Inside a comfort band
temperature<0Below freezing
humidity>70;temperature>22Warm and damp together, the mould risk
co2>800Very poor air quality
occupied==trueCurrently occupied
doorOpen==true;temperature<5A door left open in the cold

Battery and health

QueryFinds
batteryLevel<20Needs a battery before long
batteryLevel<5Needs one now
!batteryLevelDevices not reporting a battery level at all

Network quality

The fields behind the network coverage map.

QueryFinds
rssi<-115A weak signal
rssi<-124A signal at risk
snr<0Below the noise floor, with no margin
spreadingFactor>=11Slow and expensive on battery, whatever the signal
spreadingFactor>=11;rssi>-110Close to the gateway yet still transmitting expensively
!rssiDevices with no LoRa signal, so not LoRaWAN

Time

QueryFinds
reportedAt>2026-09-01Reported this month
reportedAt<2026-08-01Nothing since July - probably dead
reportedAt>2026-09-01;reportedAt<2026-09-08Reported during one week
!reportedAtInstalled but never heard from

Metadata

Your own contextual parameters, which is why filling them in at installation pays off.

QueryFinds
contextMap.floor==3Everything on floor 3
contextMap.building=='Building 2'One building
contextMap.installer==OlofEverything one person installed
contextMap.room~=meetingRooms with "meeting" in the name
!contextMap.floorDevices where nobody recorded the floor
contextMap.floor==1,2,3Floors 1, 2 or 3

Putting them together

QueryFinds
contextMap.building=='Building 2';temperature>25Too warm, in one building
deviceModelName==strips-guard;batteryLevel<20One model, due a battery
contextMap.floor==3;rssi<-115Weak coverage on one floor - the signature of a bad gateway placement
temperature;!humidityDevices reporting a temperature but no humidity, usually a translator problem
contextMap.building=='Building 2';reportedAt<2026-08-01Devices in one building that have gone quiet

If a query returns nothing

CheckWhy
The field name, character for characterField names are case-sensitive. Copy them from the Data section of a device - see Data
Whether you used == on text== is exact and case-sensitive. Try ~=
Whether a value needs quotesAnything with a space does, and so does anything that could be read as a number or a date
Whether you meant a comma or a pipeA comma lists values for one field; a pipe ORs two conditions
Whether the field is a stringA number stored as text does not answer > and <. The colour in Data tells you the type