---
title: Topic Filters
description: Learn how to use topic filters to narrow message delivery to specific entities. Subscribe with filters, manage them dynamically, and publish to filtered subscribers.
---

# 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 subscription at the infrastructure level, so filtering happens before messages reach your client, not by inspecting message payloads. This means zero wasted bandwidth.

```text
No filters (wildcard, receives everything on the topic):
subscribe("bookings")

With filter, only receives matching updates:
subscribe("bookings", { filters: ["booking_1"] })

Publish targeting filtered subscribers:
publish("bookings", data, { filter: "booking_1" })

Publish without filter:
publish("bookings", data) // only wildcard subscribers receive this
```

## Subscribe with Filters

Pass a `filters` array when subscribing to a topic:

```typescript [TypeScript]
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'
})
```
```python [Python]
from nolag import NoLag, SubscribeOptions

client = NoLag('your_access_token')
await client.connect()

room = client.set_app('dashboard').set_room('ops')

# Subscribe with filters
await room.subscribe('bookings', SubscribeOptions(
    filters=['booking_1', 'booking_2']
))

# Listen for messages
def handle_booking(data, meta):
    print('Booking update:', data)
    print('Filter:', meta.filter)

room.on('bookings', handle_booking)
```
```go [Go]
package main

nolag "github.com/NoLagApp/nolag-go"

func main() {
    client := nolag.New("your_access_token")
    client.Connect()

    room := client.SetApp("dashboard").SetRoom("ops")

    // Subscribe with filters and handler
    room.Subscribe("bookings", func(data any, meta nolag.MessageMeta) {
        fmt.Println("Booking update:", data)
        fmt.Println("Filter:", meta.Filter)
    }, nolag.SubscribeOptions{
        Filters: []string{"booking_1", "booking_2"},
    })
}
```

## 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.

```typescript [TypeScript]
// 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', [])
```
```python [Python]
# Replace all filters (full swap)
await room.set_filters('bookings', ['booking_3', 'booking_4'])

# Add filters to existing set
await room.add_filters('bookings', ['booking_5'])
# Now active: ['booking_3', 'booking_4', 'booking_5']

# Remove specific filters
await room.remove_filters('bookings', ['booking_3'])
# Now active: ['booking_4', 'booking_5']

# Remove all filters (switch to wildcard)
await room.set_filters('bookings', [])
```
```go [Go]
// Replace all filters (full swap)
room.SetFilters("bookings", []string{"booking_3", "booking_4"})

// Add filters to existing set
room.AddFilters("bookings", []string{"booking_5"})
// Now active: ["booking_3", "booking_4", "booking_5"]

// Remove specific filters
room.RemoveFilters("bookings", []string{"booking_3"})
// Now active: ["booking_4", "booking_5"]

// Remove all filters (switch to wildcard)
room.SetFilters("bookings", []string{})
```

## Publishing with Filters

Specify a `filter` when publishing to target specific subscribers:

```typescript [TypeScript]
// Publish to a specific filter
room.emit('bookings', { status: 'confirmed' }, {
  filter: 'booking_1'
})
// Only subscribers with 'booking_1' in their filters receive this

// Publish without a filter
room.emit('bookings', { type: 'system_update' })
// Only wildcard subscribers (no filters) receive this
// Filtered subscribers do NOT receive filterless publishes
```
```python [Python]
from nolag import EmitOptions

# Publish to a specific filter
await room.emit('bookings', {'status': 'confirmed'}, EmitOptions(
    filter='booking_1'
))
# Only subscribers with 'booking_1' in their filters receive this

# Publish without a filter
await room.emit('bookings', {'type': 'system_update'})
# Only wildcard subscribers (no filters) receive this
```
```go [Go]
// Publish to a specific filter
room.Emit("bookings", map[string]string{"status": "confirmed"}, nolag.EmitOptions{
    Filter: "booking_1",
})
// Only subscribers with "booking_1" in their filters receive this

// Publish without a filter
room.Emit("bookings", map[string]string{"type": "system_update"})
// Only wildcard subscribers (no filters) receive this
```

**Important:** Messages published without a filter are only delivered to wildcard (no-filter) subscribers. Filtered subscribers do NOT receive filterless publishes. Use a separate topic for broadcasts if needed.

## 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:

```typescript [TypeScript]
// 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
- Filter values must **not** contain `/`, `#`, or `+` (reserved characters)
- 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 | Subscribe without filters (wildcard) |
| Receive updates for specific entities | Subscribe with filters |
| Change which entities to track | `setFilters` / `addFilters` / `removeFilters` |
| Broadcast to all subscribers | Use a separate topic (filtered subscribers won't receive filterless publishes) |

## Next Steps

- [Topics & Pub/Sub](/docs/concepts/topics)
- [Rooms](/docs/concepts/rooms)
- [JavaScript SDK Reference](/docs/sdks/javascript)
- [Live Dashboards Guide](/docs/guides/dashboards)
