Skip to main content

Filtering

Filters narrow the stream to only the aircraft you care about. They run server-side, so a tight filter reduces both bandwidth and per-batch processing on your client.

Filters are set when you connect

A filter is supplied with the connection request — the filters field of StartStreamRequest. The gateway compiles it at connect time and applies it to that stream:

  • Every connection starts from its own filters value. Nothing set on a previous connection carries over — a reconnect is filtered by exactly what its StartStream request says.
  • Omitting filters, or sending an empty filters message ({}), means "no filter": the stream is unfiltered.
  • To change the filter, reconnect with a new filters value. During the deprecation period you can also change it in place with UpdateSubscription (see Changing filters via UpdateSubscription below).

Filter shape

A filter is a JSON document with a single rootGroup. A group combines an op (logical operator) with a list of leaf conditions and optional nested sub-groups:

{
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "alt_baro", "op": "lt", "value": "5000" }
],
"groups": []
}
}

A group has:

FieldDescription
opAND or OR — how the entries below are combined.
conditionsArray of leaf conditions (see below).
groupsArray of nested sub-groups, each itself a group with the same shape.

A condition has:

FieldDescription
fieldThe aircraft JSON field name (e.g. alt_baro, hex, flight, gs). See Aircraft for the full list.
opOne of the comparison operators listed below.
valueThe value to compare against, as a string (numeric values are accepted as numeric strings).
proto3 JSON field names

proto3 JSON accepts either the camelCase or the original snake_case field name — subscriptionId and subscription_id are both valid on the wire. The examples on this page use camelCase throughout; prefer it for new code.

Operators

OperatorDescription
eqEquals
neNot equals
gtGreater than
geGreater than or equal
ltLess than
leLess than or equal
containsString contains (case-insensitive)
notcontainsString does not contain
startswithString starts with
notstartswithString does not start with
endswithString ends with
notendswithString does not end with
isnullProperty is missing or null
isnotnullProperty is present and not null

String comparisons are case-insensitive. Numeric comparisons apply to fields whose values are numbers (alt_baro, gs, lat, lon, etc.). For alt_baro, the value "ground" is treated as 0 — so alt_baro lt 1000 will include grounded aircraft.

Examples

Aircraft below 5,000 ft

{
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "alt_baro", "op": "lt", "value": "5000" }
]
}
}

Airborne aircraft below 5,000 ft moving faster than 100 kt

Combines two conditions with AND. Useful for excluding stationary or taxiing aircraft from a low-altitude filter.

{
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "alt_baro", "op": "lt", "value": "5000" },
{ "field": "alt_baro", "op": "ne", "value": "ground" },
{ "field": "gs", "op": "gt", "value": "100" }
]
}
}

Specific aircraft by ICAO hex

{
"rootGroup": {
"op": "OR",
"conditions": [
{ "field": "hex", "op": "eq", "value": "a1b2c3" },
{ "field": "hex", "op": "eq", "value": "d4e5f6" }
]
}
}

Nested groups: heavy aircraft OR fast aircraft

Use the groups array on a parent group to combine sub-expressions with mixed AND/OR. Here: aircraft categorised as heavy (category A5), or any aircraft above Mach 0.85.

{
"rootGroup": {
"op": "OR",
"groups": [
{
"op": "AND",
"conditions": [
{ "field": "category", "op": "eq", "value": "A5" }
]
},
{
"op": "AND",
"conditions": [
{ "field": "mach", "op": "gt", "value": "0.85" }
]
}
]
}
}

Aircraft type code matches a pattern

{
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "t", "op": "startswith", "value": "B7" }
]
}
}

Matches Boeing 7-series types (B737, B738, B744, B777, B788, etc.).

Applying a filter over gRPC

Pass filters directly on the StartStream request. The gateway compiles it once when the stream connects and applies it for the life of that stream.

Full grpcurl invocation that applies the "below 5,000 ft" filter from the first example:

grpcurl \
-import-path . -proto aircraft.proto -proto filter.proto \
-H "client-id: <CLIENT_ID>" \
-d '{
"subscriptionId": "<SUB_ID>",
"filters": {
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "alt_baro", "op": "lt", "value": "5000" }
]
}
}
}' \
stream.adsbexchange.com:443 \
jetnet.aircraft.AircraftStreamingService/StartStream

To change the filter, reconnect with a different filters value on the next StartStream call. To remove a filter, reconnect with "filters": {} or omit the field.

Changing filters via UpdateSubscription

Deprecated

SubscriptionService.UpdateSubscription is a deprecated way to change the filter of a stream that is already open, kept for backward compatibility. It is marked deprecated in the proto and will be removed in a future release.

How it behaves:

  • It replaces the filter on the subscription's open stream. The change takes effect on the gateway's next revalidation pass — up to 5 minutes in production.
  • Omitting filters on the call removes the filter, so the open stream becomes unfiltered.
  • It does not outlive the connection. When the stream disconnects and reconnects, the new connection is filtered by its own StartStream filters value only.

Example:

grpcurl \
-import-path . -proto subscription.proto \
-H "client-id: <YOUR_CLIENT_ID>" \
-d '{
"subscriptionId": "<YOUR_SUBSCRIPTION_ID>",
"filters": {
"rootGroup": {
"op": "AND",
"conditions": [
{ "field": "alt_baro", "op": "lt", "value": "5000" }
]
}
}
}' \
stream.adsbexchange.com:443 \
jetnet.streaming.SubscriptionService/UpdateSubscription

Migrating off UpdateSubscription: take the same filter JSON you were sending to UpdateSubscription and move it onto the filters field of your StartStream request instead (see Applying a filter over gRPC above). To change a filter later, reconnect with the new value. The filter shape is identical.