Access Scopes - How They Work
Scopes follow the actor, not the room or the app. This one design choice makes multi-tenancy simple.
Core Concept: The Scope Follows the Actor
Access Scopes are not attached to rooms or apps. They are attached to actors. When an actor connects and subscribes to topics, the scope slug is automatically injected into the topic namespace by the backend. The actor, the SDK, and the Kraken proxy do not need to know about scopes at all.
This means you can add multi-tenant isolation to an existing application by creating scopes and assigning actors to them. No code changes, no room duplication, no SDK upgrades.
Topic Namespace Diagram
The scope slug is inserted between the app slug and the room slug in the MQTT topic path:
Without Scope (unscoped actor)
app-slug / room-slug / topic-name
| | |
app room topicWith Scope (scoped actor)
app-slug / scope-slug / room-slug / topic-name
| | | |
app scope room topic Because the scope slug is part of the topic path, MQTT subscription filters naturally partition traffic. An actor subscribed to my-app/client-acme/chat/messages will never receive messages published to my-app/client-beta/chat/messages. The isolation is enforced at the protocol level.
Data Model
Scopes are a project-level resource. They exist at the same level as apps and actors in the NoLag hierarchy:
Organization
|-- Project
|-- App (has rooms, topics)
|-- Actor (has accessScopeId FK, nullable)
|-- Access Scope (slug, name)- An actor has an optional
accessScopeIdforeign key pointing to an Access Scope - If
accessScopeIdisnull, the actor is unscoped and uses the default topic namespace - If
accessScopeIdis set, the actor is scoped and its topics include the scope slug
Zero Kraken/SDK Changes Needed
The Kraken proxy and the client SDKs treat MQTT topics as opaque strings. They do not parse or interpret the topic structure. The scope slug is injected by the backend when constructing the topic paths for a scoped actor. This means:
- No Kraken proxy changes - it forwards messages based on topic subscriptions as usual
- No SDK changes -
@nolag/js-sdkconnects and subscribes to whatever topics are configured - No EMQX broker changes - standard MQTT topic filtering handles the isolation
Key Rules
| Rule | Details |
|---|---|
| One scope per actor | An actor can belong to at most one scope at a time. To move an actor to a different scope, update the accessScopeId field. |
| Nullable FK | The accessScopeId is optional. Actors without a scope use the default (unscoped) topic namespace and can communicate with other unscoped actors. |
| Slug is immutable | Once a scope is created, its slug cannot be changed. The slug is part of the MQTT topic path, so changing it would break existing subscriptions. The name field can be updated freely. |
| Project-level resource | Scopes belong to a project, not to an individual app. All apps in the project use the same set of scopes. |
| Scoped actors cannot see unscoped actors | A scoped actor and an unscoped actor are in different topic namespaces. They cannot communicate even if they are in the same room. |
Next Steps
- Getting Started - create your first scope and assign actors
- Multi-Tenancy Patterns - real-world patterns for tenant isolation
- API Reference - complete endpoint documentation