Realtime has two layers of protection:
  1. Channel rules decide who may subscribe to and publish on channels whose name matches a pattern.
  2. Record changes (resource:<table>) are additionally filtered per row by the table’s own read rule.

Channel rules

Studio: Services → Realtime → Channel rules → Add rule. The pattern (such as chat:*) chooses which channels it covers; then a subscribe rule and a publish rule, using the rule picker. Rules see $auth and the usual request facts. They do not see a record: a channel isn’t a row.

Designs

One inbox per user

A channel per user (user:<id>) where only your server publishes works well. The subscribe rule must also ensure a person joins only their own. A pattern-only JSON rule can’t compare the channel name with the user’s id, so for this design ask your team to register a code rule (owns_user_channel) that checks the name, and use it as the subscribe rule. Without it, any signed-in user could join anyone’s user:* channel. An alternative that needs no code: publish to a channel named with an unguessable value stored on the user (inbox:<random>), and give each person their own.

Live records

Subscribing to resource:<table> needs the table to broadcast and the resource to be readable. What arrives is decided per row: the table’s read rule is checked against the row as it was and as it is now, for each subscriber. So:
  • Ada hears about her notes; Bob about his.
  • A row handed to someone else stops going to the old owner.
  • Write-only columns never leave.
Nobody can publish to resource:*. See Live records.

Refusals

subscribe() and send() throw RealtimeError when a rule refuses. Treat it like any permission error: tell the person, don’t retry in a loop.

Test it

Two signed-in users and a visitor try to join and send on each pattern. The server publishing with a secret key must always work.

Channel rules

All the settings.

Rules that need more data

For what the builder can’t see.