Topic Filters
Filters let you narrow message delivery to specific entities within a topic. Instead of receiving all updates, subscribe with filters and only get the messages you care about.
Why Filters?
Without filters, subscribing to a topic delivers every message published to it. For a dashboard showing 3 bookings out of thousands, this means receiving updates for all bookings and discarding most of them client-side.
Filters solve this at the infrastructure level. Subscribe to bookings with filters ["booking_1", "booking_2"] and only updates for those two bookings are delivered. When the user navigates to different bookings, swap filters dynamically, no resubscribe needed.
How It Works
Each filter value becomes a separate routing key at the infrastructure level, so filtering happens before messages reach your client, not by inspecting message payloads. This means zero wasted bandwidth.
A subscription without filters is a wildcard: it receives every message on the topic, filtered or not. A subscription with filters receives only messages published with one of its filter values. A message published without a filter reaches only the wildcard subscribers.
No filters (wildcard, receives everything on the topic, including filtered publishes):
subscribe("bookings")
With filters, receives only publishes carrying one of these values:
subscribe("bookings", { filters: ["booking_1"] })
Publish with a filter: reaches subscribers filtering on "booking_1" AND every wildcard subscriber
publish("bookings", data, { filter: "booking_1" })
Publish without a filter: reaches wildcard subscribers only
publish("bookings", data)
Subscribe with Filters
Pass a filters array when subscribing to a topic:
import { NoLag } from '@nolag/js-sdk'
const client = NoLag('your_access_token')
await client.connect()
const room = client.setApp('dashboard').setRoom('ops')
// Subscribe with filters: only receive updates for these bookings
room.subscribe('bookings', {
filters: ['booking_1', 'booking_2']
})
// Listen for messages (only matching filters arrive)
room.on('bookings', (data, meta) => {
console.log('Booking update:', data)
console.log('Filter:', meta.filter) // e.g. 'booking_1'
})Dynamic Filter Management
Change filters at any time without unsubscribing and resubscribing. New filters are subscribed before old ones are removed to minimize message gaps.
// Replace all filters (full swap)
room.setFilters('bookings', ['booking_3', 'booking_4'])
// Add filters to existing set
room.addFilters('bookings', ['booking_5'])
// Now active: ['booking_3', 'booking_4', 'booking_5']
// Remove specific filters
room.removeFilters('bookings', ['booking_3'])
// Now active: ['booking_4', 'booking_5']
// Remove all filters (switch to wildcard, receives everything)
room.setFilters('bookings', [])Publishing with Filters
Specify a filter when publishing to target specific subscribers:
// Publish to a specific filter
room.emit('bookings', { status: 'confirmed' }, {
filter: 'booking_1'
})
// Reaches subscribers with 'booking_1' in their filters,
// plus every wildcard (no-filter) subscriber
// Publish without a filter
room.emit('bookings', { type: 'system_update' })
// Reaches wildcard subscribers only
// Filtered subscribers do NOT receive filterless publishesTwo things to plan for. Messages published without a filter are only delivered to wildcard (no-filter) subscribers; filtered subscribers do NOT receive them, so use a separate topic for broadcasts if needed. And a wildcard subscriber receives everything, including every filtered publish: filters narrow what a subscriber gets, they are not a privacy boundary. Use rooms and access control for that.
AND Groups
A filter entry can itself be a list, which matches only messages tagged with all of its values. Publish to an AND group with filters (plural); the broker sorts and lowercases the values and joins them into one routing key.
// (alice AND admin) OR bob
room.subscribe('bookings', {
filters: [['alice', 'admin'], 'bob']
})
// Reaches subscribers whose group is exactly {alice, admin}, plus wildcard subscribers
room.emit('bookings', { status: 'confirmed' }, { filters: ['admin', 'alice'] })Example: Real-time Dashboard
The most common use case for filters is dashboards where users view a subset of entities that update in real-time:
// Real-time dashboard: user views 3 bookings out of thousands
const room = client.setApp('dashboard').setRoom('ops')
// Subscribe only to the bookings currently visible
const visibleBookingIds = ['booking_42', 'booking_87', 'booking_153']
room.subscribe('bookings', {
filters: visibleBookingIds
})
room.on('bookings', (data, meta) => {
updateBookingCard(meta.filter, data)
})
// User navigates to a different page of bookings
function onPageChange(newBookingIds: string[]) {
room.setFilters('bookings', newBookingIds)
}
// User opens a booking detail view, add it to filters
function onBookingOpen(bookingId: string) {
room.addFilters('bookings', [bookingId])
}
// User closes a booking detail view, remove it
function onBookingClose(bookingId: string) {
room.removeFilters('bookings', [bookingId])
}Filter Rules
- Max 100 filters per topic per connection; more is refused with
too_many_filters (max 100) - Filter values must not be empty or contain
/,#,+or|; the broker refuses the subscribe withinvalid_filter_chars (/, #, +, | not allowed)on theerrorevent - An AND group needs at least two values
- Filters are persisted, on reconnect, your filters are automatically restored
- Filters work with load balancing, each filter topic uses the shared subscription group
- Setting filters to an empty array switches back to wildcard mode (receives everything)
Filters vs Wildcards
| Scenario | Approach |
|---|---|
| Receive all messages on a topic, filtered or not | Subscribe without filters (wildcard) |
| Receive updates for specific entities | Subscribe with filters |
| Change which entities to track | setFilters / addFilters / removeFilters |
| Broadcast to every subscriber including filtered ones | Use a separate topic (filtered subscribers won't receive filterless publishes) |
| Hide one entity's messages from other subscribers | Filters cannot do this; use a room per entity and access control |