---
title: Access Scopes - How They Work
description: Understand how Access Scopes provide tenant isolation through topic namespacing. Learn the data model, key rules, and why no SDK changes are needed.
---

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

```text
app-slug / room-slug / topic-name
   |           |            |
   app         room         topic
```

### With Scope (scoped actor)

```text
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:

```text
Organization
  |-- Project
       |-- App (has rooms, topics)
       |-- Actor (has accessScopeId FK, nullable)
       |-- Access Scope (slug, name)
```

- An actor has an optional `accessScopeId` foreign key pointing to an Access Scope
- If `accessScopeId` is `null`, the actor is **unscoped** and uses the default topic namespace
- If `accessScopeId` is 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-sdk` connects 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](/docs/scopes/getting-started) - create your first scope and assign actors
- [Multi-Tenancy Patterns](/docs/scopes/multi-tenancy) - real-world patterns for tenant isolation
- [API Reference](/docs/scopes/api-reference) - complete endpoint documentation
