> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nodalmerge.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> Scope what your client materializes and receives with path-pattern subscriptions to reduce bandwidth and UI noise.

# Subscriptions

Subscriptions control which paths a client materializes and receives during sync.

They are a visibility/bandwidth mechanism, not an authority mechanism.

## Why subscriptions exist

Large rooms often contain more state than one client view needs.

Subscriptions help by:

* Reducing client-side materialization scope
* Reducing relayed traffic for non-matching paths
* Keeping UI selectors focused on relevant data

## Initial subscription

Set subscription patterns at document creation:

```ts theme={null}
const doc = await createDoc({
  serverUrl,
  room,
  subscribe: ["world/**", "notes/welcome"]
});
```

Default is broad (`["**"]`) when no subscription is provided.

## Runtime subscription updates

Update patterns dynamically:

```ts theme={null}
doc.subscribe(["world/**"]);
doc.onSubscriptionChange(({ patterns }) => {
  rerender(patterns);
});
```

Use runtime updates for route/view changes in single-page apps.

## Path checks

Use helper checks before expensive reads:

```ts theme={null}
doc.isSubscribed("world/player1"); // true/false
```

This is useful for conditional data access and UI guardrails.

## Glob semantics (practical)

Typical pattern behavior:

* `**` matches everything
* `foo/**` matches `foo` and descendants
* `*` matches one path segment

Keep patterns simple and intentional; over-broad patterns defeat subscription benefits.

## What subscriptions affect

Subscriptions affect client materialization and relay filtering behavior.

They do **not** replace write authority enforcement.

Authority remains governed by policy/capability rules.

## Design pattern: route-scoped subscriptions

In UI apps:

1. Map each route/view to a small set of patterns
2. Apply `doc.subscribe(...)` on route activation
3. Expand only when user workflows demand wider context

This keeps bandwidth and memory pressure predictable.

## Debugging subscription issues

If expected updates are missing:

1. Confirm path actually matches active patterns
2. Check runtime subscription updates are applied
3. Verify view code is not reading outside subscribed scope
4. Confirm issue is visibility-scope related, not authority rejection

## Common mistakes

* Treating subscription as access control
* Leaving stale narrow patterns active after navigation
* Using complex overlapping patterns without tests
* Assuming `doc.onChange` always implies subscribed materialized changes

## Runtime SDK event subscriptions

The runtime SDK (`createNodalMergeSdk`) uses a separate event subscription model via `sdk.on(event, handler)`. These are process-level lifecycle and protocol events, not path-pattern subscriptions.

```ts theme={null}
const stop = sdk.on("connected", () => {
  console.log("room connected");
});

// Unsubscribe by calling the returned function
stop();
```

### `NodalMergeSdkEvent` reference

| Event               | Payload                                                    | When it fires                                               |
| ------------------- | ---------------------------------------------------------- | ----------------------------------------------------------- |
| `"connected"`       | none                                                       | WebSocket session opened and `welcome` received             |
| `"disconnected"`    | none                                                       | WebSocket connection closed (clean or error)                |
| `"reconnect"`       | `{ attempt: number }`                                      | Reconnect attempt starting (with backoff)                   |
| `"state"`           | `{ state: "connecting" \| "connected" \| "disconnected" }` | Any connection state transition                             |
| `"message"`         | raw WebSocket message string                               | Every inbound WebSocket frame (low-level)                   |
| `"error"`           | `{ msg: string }`                                          | Server-sent `error` envelope received                       |
| `"presence"`        | presence envelope object                                   | Presence update received from server                        |
| `"signal"`          | WebRTC signal envelope                                     | `webrtc-offer`, `webrtc-answer`, or `webrtc-ice` received   |
| `"runtime-message"` | `NodalMergeRuntimeMessage`                                 | Any parsed server message that passes `parseRuntimeMessage` |
| `"transport"`       | `{ active: "ws-only" \| "ws+webrtc" }`                     | Active transport layer changes                              |

### Common patterns

**Connection lifecycle**:

```ts theme={null}
sdk.on("state", ({ state }) => {
  setConnectionState(state); // "connecting" | "connected" | "disconnected"
});
```

**Error and rejection handling**:

```ts theme={null}
sdk.on("error", ({ msg }) => {
  console.error("server error:", msg);
  // msg may be "reject.control_plane_forbidden: ...", "auth: ...", etc.
});
```

**Observing all runtime messages**:

```ts theme={null}
sdk.on("runtime-message", (msg) => {
  // msg.type is a NodalMergeRuntimeMessageType, e.g.:
  // "projection.build.completed", "replay.read-range.result", "pack", etc.
  if (msg.type === "projection.build.completed") {
    handleProjectionReady(msg);
  }
});
```

**Presence updates**:

```ts theme={null}
sdk.on("presence", (envelope) => {
  updateCursorLayer(envelope);
});
```

**WebRTC signaling**:

```ts theme={null}
sdk.on("signal", (signal) => {
  if (signal.type === "webrtc-offer") {
    handleOffer(signal.from, signal.sdp);
  }
});
```

`NodalMergeRuntimeMessage.type` values are exhaustively typed in `NodalMergeRuntimeMessageType`. Use a `switch` or type-narrowed handler rather than string comparison against arbitrary values.

## Related pages

* [sdk/javascript](/sdk/javascript)
* [sdk/presence](/sdk/presence)
* [protocol/synchronization](/protocol/synchronization)
