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
filtersvalue. Nothing set on a previous connection carries over — a reconnect is filtered by exactly what itsStartStreamrequest says. - Omitting
filters, or sending an emptyfiltersmessage ({}), means "no filter": the stream is unfiltered. - To change the filter, reconnect with a new
filtersvalue. During the deprecation period you can also change it in place withUpdateSubscription(see Changing filters viaUpdateSubscriptionbelow).
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:
| Field | Description |
|---|---|
op | AND or OR — how the entries below are combined. |
conditions | Array of leaf conditions (see below). |
groups | Array of nested sub-groups, each itself a group with the same shape. |
A condition has:
| Field | Description |
|---|---|
field | The aircraft JSON field name (e.g. alt_baro, hex, flight, gs). See Aircraft for the full list. |
op | One of the comparison operators listed below. |
value | The value to compare against, as a string (numeric values are accepted as numeric strings). |
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
| Operator | Description |
|---|---|
eq | Equals |
ne | Not equals |
gt | Greater than |
ge | Greater than or equal |
lt | Less than |
le | Less than or equal |
contains | String contains (case-insensitive) |
notcontains | String does not contain |
startswith | String starts with |
notstartswith | String does not start with |
endswith | String ends with |
notendswith | String does not end with |
isnull | Property is missing or null |
isnotnull | Property 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
filterson 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
StartStreamfiltersvalue 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.