Documentation Index Fetch the complete documentation index at: https://mintlify.com/sockudo/sockudo/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Tag filtering allows clients to subscribe to channels with server-side filters, so they only receive messages matching specific criteria. This dramatically reduces bandwidth usage and processing overhead, particularly beneficial for mobile devices and high-volume channels.
Bandwidth Savings: 60-90% in high-volume scenarios
Tag filtering is disabled by default for Pusher backward compatibility. You must explicitly enable it via configuration.
Quick Start
1. Enable Tag Filtering
Environment Variable:
TAG_FILTERING_ENABLED = true
Configuration File:
{
"tag_filtering" : {
"enabled" : true
}
}
curl -X POST http://localhost:6001/apps/my-app/events \
-H "Content-Type: application/json" \
-d '{
"name": "match_event",
"channel": "match:123",
"data": "{\"minute\":\"23\",\"player\":\"Mbappe\"}",
"tags": {
"event_type": "goal",
"team": "Real Madrid"
}
}'
3. Subscribe with Filter (Client)
import Pusher , { Filter } from 'sockudo-js' ;
const pusher = new Pusher ( 'my-app-key' , {
wsHost: 'localhost' ,
wsPort: 6001
});
// Only receive goal events
const channel = pusher . subscribe ( 'match:123' , Filter . eq ( 'event_type' , 'goal' ));
channel . bind ( 'match_event' , ( data ) => {
console . log ( 'Goal!' , data );
// This will ONLY fire for goals, not shots, passes, etc.
});
How It Works
Architecture
Tags on Publications : Each publication can have optional tags (key-value string pairs)
Filters on Subscriptions : Clients specify filters when subscribing to channels
Evaluation During Broadcast : Filters are evaluated with zero allocations during message broadcast
Filtered Delivery : Only messages matching the filter are sent to that subscriber
Compilation : Filter validation happens once at subscription time (~100-200ns)
Evaluation : Single filter evaluation takes ~10-95ns depending on complexity
Memory : Zero allocations per evaluation (hot path optimization)
Scale : 10k subscribers evaluated in ~100-900μs depending on filter complexity
Filter Operators
Comparison Operators
Operator Description Example eqEquality Filter.eq('event_type', 'goal')neqInequality Filter.neq('event_type', 'pass')inSet membership Filter.in('event_type', ['goal', 'shot'])ninSet non-membership Filter.nin('event_type', ['pass', 'tackle'])gtGreater than (numeric) Filter.gt('xG', '0.8')gteGreater than or equal Filter.gte('price', '100.00')ltLess than (numeric) Filter.lt('count', '10')lteLess than or equal Filter.lte('score', '3')
String Operations
Operator Description Example startsWithString starts with Filter.startsWith('ticker', 'GOOG')endsWithString ends with Filter.endsWith('ticker', 'L')containsString contains Filter.contains('description', 'important')
Existence Checks
Operator Description Example existsKey exists in tags Filter.exists('xG')notExistsKey does not exist Filter.notExists('penalty')
Logical Operators
Operator Description Example andAll must match Filter.and(Filter.eq('type', 'shot'), Filter.gte('xG', '0.8'))orAt least one must match Filter.or(Filter.eq('type', 'goal'), Filter.eq('type', 'penalty'))notNegates filter Filter.not(Filter.eq('type', 'pass'))
Filter Examples
Simple Filters
// Single equality
Filter . eq ( 'event_type' , 'goal' )
// Multiple event types
Filter . in ( 'event_type' , [ 'goal' , 'penalty' , 'red_card' ])
// Numeric threshold
Filter . gte ( 'price' , '100.00' )
// String pattern
Filter . startsWith ( 'ticker' , 'GOOG' )
Complex Filters
Football Match Events - Goals OR high-probability shots:
const filter = Filter . or (
Filter . eq ( 'event_type' , 'goal' ),
Filter . and (
Filter . eq ( 'event_type' , 'shot' ),
Filter . gte ( 'xG' , '0.8' )
)
);
const channel = pusher . subscribe ( 'match:123' , filter );
Stock Market Tickers - Specific tickers with price threshold:
const filter = Filter . and (
Filter . in ( 'ticker' , [ 'GOOGL' , 'AAPL' , 'MSFT' ]),
Filter . gte ( 'price' , '100.00' )
);
const channel = pusher . subscribe ( 'stock-prices' , filter );
IoT Sensor Data - Critical sensor readings:
const filter = Filter . or (
Filter . and (
Filter . eq ( 'sensor_type' , 'temperature' ),
Filter . gt ( 'value' , '100' )
),
Filter . and (
Filter . eq ( 'sensor_type' , 'pressure' ),
Filter . lt ( 'value' , '20' )
)
);
const channel = pusher . subscribe ( 'iot-sensors' , filter );
Use Cases
1. Sports Live Scores
Problem: Mobile apps don’t need every pass/tackle, just important events
Solution:
// Client only interested in goals and red cards
const filter = Filter . in ( 'event_type' , [ 'goal' , 'red_card' ]);
const channel = pusher . subscribe ( 'match:live' , filter );
Result: 90% bandwidth reduction on high-activity matches
2. Stock Market Real-Time Data
Problem: Trading apps track 10-20 stocks from thousands available
Solution:
// Only track watchlist stocks
const filter = Filter . and (
Filter . in ( 'ticker' , watchlist ),
Filter . gte ( 'price' , '50.00' )
);
const channel = pusher . subscribe ( 'stocks' , filter );
Result: 95% bandwidth reduction, lower data costs
3. IoT Monitoring
Problem: Dashboard only needs critical alerts, not all sensor readings
Solution:
// Only critical/high severity
const filter = Filter . or (
Filter . eq ( 'severity' , 'critical' ),
Filter . eq ( 'severity' , 'high' )
);
const channel = pusher . subscribe ( 'alerts' , filter );
Result: 80% bandwidth reduction, reduced alert fatigue
4. Gaming Events
Problem: Players only care about rare drops and personal achievements
Solution:
// High-value drops and achievements
const filter = Filter . or (
Filter . gte ( 'item_rarity' , '4' ),
Filter . eq ( 'type' , 'achievement' )
);
const channel = pusher . subscribe ( 'game-events' , filter );
Result: 70% bandwidth reduction, better UX
Problem: Users follow specific topics/hashtags, not all content
Solution:
// Filter by hashtag
const filter = Filter . contains ( 'hashtags' , 'breaking' );
const channel = pusher . subscribe ( 'feed' , filter );
Result: 85% bandwidth reduction, relevant content only
Simple Filter: event_type == "goal"
Operation Time Allocations Compilation (subscription) 103.5ns 3 allocs (328 bytes) Single evaluation 11.69ns 0 allocs 10k evaluations (broadcast) 112.08μs 0 allocs
Complex Filter: (event_type == "shot" AND xG >= "0.8") AND (count > 42 OR price >= 99.5)
Operation Time Allocations Compilation (subscription) 206.8ns 5 allocs (664 bytes) Single evaluation 93.78ns 0 allocs 10k evaluations (broadcast) 923.83μs 0 allocs
Bandwidth Savings
Scenario Without Filtering With Filtering Savings High-volume sports channel 1000 msg/sec 50-100 msg/sec 90% Stock market data 10,000 msg/sec 200-500 msg/sec 95% IoT sensor alerts 500 msg/sec 50-100 msg/sec 80%
Best Practices
1. Design Your Tag Schema
Plan your tag structure based on how clients will filter:
// Good: Filterable tags
tags : {
"event_type" : "goal" ,
"team" : "Real Madrid" ,
"player_id" : "9" ,
"minute" : "23"
}
// Bad: Nested or complex structures
tags : {
"metadata" : "{ \" event \" : \" goal \" , \" team \" : \" Real Madrid \" }"
}
2. Keep Filters Simple
Simpler filters evaluate faster:
// Good: Simple filter
Filter . eq ( 'event_type' , 'goal' )
// OK: Moderate complexity
Filter . or (
Filter . eq ( 'event_type' , 'goal' ),
Filter . eq ( 'event_type' , 'penalty' )
)
// Avoid: Overly complex nested filters
Filter . and (
Filter . or ( ... many nodes ... ),
Filter . or ( ... many nodes ... ),
Filter . not ( Filter . and ( ... many nodes ... ))
)
3. Use Set Operations Efficiently
For multiple values, use in instead of multiple or conditions:
// Good
Filter . in ( 'event_type' , [ 'goal' , 'shot' , 'penalty' ])
// Less efficient
Filter . or (
Filter . eq ( 'event_type' , 'goal' ),
Filter . eq ( 'event_type' , 'shot' ),
Filter . eq ( 'event_type' , 'penalty' )
)
4. Validate Filters Client-Side
Catch filter errors early:
import { validateFilter } from 'sockudo-js' ;
const filter = buildFilterFromUserInput ();
const error = validateFilter ( filter );
if ( error ) {
showErrorToUser ( error );
return ;
}
pusher . subscribe ( channelName , filter );
5. Consider Message Volume
Filters are most beneficial when:
Channel has high message volume (>100 messages/second)
Clients need only a small subset (<20% of messages)
Many concurrent subscribers with different filter needs
Combining with Delta Compression
Tag filtering and delta compression work seamlessly together for maximum efficiency:
// Enable delta compression + tag filtering
pusher . connection . bind ( 'connected' , () => {
pusher . connection . send_event ( 'pusher:enable_delta_compression' , {});
});
const channel = pusher . subscribe ( 'market-data' , {
filter: Filter . in ( 'ticker' , [ 'GOOGL' , 'AAPL' , 'MSFT' ]),
delta: { enabled: true , algorithm: 'Fossil' }
});
Result: 95%+ total bandwidth reduction (filtering + compression)
Limitations
Tags must exist on publication : Messages without tags will not match any filter (even existence checks)
String-based values : All tag values are strings (numeric comparisons parse strings as needed)
No regex support : Use startsWith, endsWith, contains for pattern matching
Filter size : Keep filters reasonably sized (recommended <50 nodes)
No security boundary : Filters don’t add permission checks, they only reduce bandwidth
Dynamic Filter Updates
To change filters, unsubscribe and resubscribe:
// Initial subscription with one filter
let channel = pusher . subscribe ( 'match:123' , Filter . eq ( 'event_type' , 'goal' ));
// Later, change filter
pusher . unsubscribe ( 'match:123' );
channel = pusher . subscribe ( 'match:123' , Filter . in ( 'event_type' , [ 'goal' , 'shot' ]));
Troubleshooting
Messages Not Being Received
Check tags exist: Ensure publications have the tags you’re filtering on
# Verify tags in publication
curl -X POST http://localhost:6001/apps/my-app/events \
-d '{
"name": "event",
"channel": "test",
"data": "{}",
"tags": {"type": "test"} # Tags present
}'
Validate filter: Use validateFilter() to check for syntax errors
import { validateFilter } from 'sockudo-js' ;
const filter = Filter . eq ( 'event_type' , 'goal' );
const error = validateFilter ( filter );
if ( error ) {
console . error ( 'Invalid filter:' , error );
}
Test without filter: Verify messages arrive without filter first
// Test without filter
const channel = pusher . subscribe ( 'match:123' );
channel . bind ( 'match_event' , ( data ) => {
console . log ( 'Received:' , data );
});
Filter Not Working as Expected
Case sensitivity: All comparisons are case-sensitive
// These are different
Filter . eq ( 'type' , 'Goal' ) // Won't match 'goal'
Filter . eq ( 'type' , 'goal' ) // Correct
String vs numeric: Numeric operators parse strings
// Both work (values are strings in tags)
Filter . gt ( 'count' , '10' )
Filter . gt ( 'count' , 10 ) // Converted to string
Missing tags: Messages without tags won’t match any filter
Simplify filters: Reduce nesting depth and number of nodes
Use appropriate operators: in for sets, not multiple or conditions
Check tag count: Fewer tags per message = faster evaluation
Migration Guide
Adding Filters to Existing Application
1. Enable tag filtering (backward compatible - old clients ignore tags)
TAG_FILTERING_ENABLED = true
2. Add tags to publications (existing clients continue to work)
# Start adding tags to new publications
curl -X POST http://localhost:6001/apps/my-app/events \
-d '{
"tags": {"event_type": "goal"}
}'
3. Update clients to use new filter API when subscribing
// New clients with filtering
const channel = pusher . subscribe ( 'match:123' , Filter . eq ( 'event_type' , 'goal' ));
// Old clients without filtering (still work)
const channel = pusher . subscribe ( 'match:123' );
4. Monitor bandwidth to measure savings
5. Gradually roll out filters to different client segments
Next Steps
Delta Compression Combine with delta compression for 95%+ bandwidth reduction
Rate Limiting Control message throughput per application