---
title: "Migrate from Pusher to NoLag"
description: A practical guide to moving a realtime app from Pusher Channels to NoLag, with a concept map and before-and-after code for subscribing and publishing.
excerpt: "Pusher and NoLag share the same pub/sub shape, so most migrations are a rename of a few concepts. Here is the mapping and the code, side by side."
date: '2026-07-29'
readTime: 6 min read
category: Migration Guide
---

Pusher Channels popularised hosted realtime pub/sub, and a lot of apps still run on it. If you are moving to NoLag, the good news is that the mental model is almost identical: you subscribe to a channel, you bind to events, and a publisher sends data to everyone listening. This guide maps Pusher concepts onto NoLag and shows the same subscribe-and-publish flow in both.

## Why teams move

NoLag keeps the pub/sub foundation you already rely on and adds a few things Pusher does not focus on:

- **Blueprint SDKs** for chat, notifications, dashboards, tracking, and more, so common patterns are a few lines instead of a from-scratch build.
- **A coordination layer for AI agents**: dispatch work, share state, observe decisions, and gate actions with human approval, on the same infrastructure as your app.
- **Per-message QoS** on the broker hop, and presence built in.

## Concept map

| Pusher | NoLag |
| --- | --- |
| App key + cluster | App (a container for rooms and topics) |
| Channel | Room + topic |
| `channel.bind('event', cb)` | `room.on('topic', cb)` |
| `pusher.trigger('channel', 'event', data)` | `room.emit('topic', data)` |
| Presence channel | Presence (per room) |
| Private channel + auth endpoint | Actors, access tokens, and per-topic ACL |

## Before: Pusher

```js
import Pusher from 'pusher-js'

const pusher = new Pusher('APP_KEY', { cluster: 'eu' })

const channel = pusher.subscribe('chat')
channel.bind('message', (data) => {
  console.log('Received:', data)
})

// On your server, using the pusher server SDK:
// pusher.trigger('chat', 'message', { text: 'Hello!' })
```

## After: NoLag

```ts
import { NoLag } from '@nolag/js-sdk'

// Create the app and its `general` room in the control plane first. The app
// slug you get back has a random suffix (for example `chat-a3f9`): that is
// what you pass to setApp(). Room slugs are kept verbatim.
const APP_SLUG = 'chat-a3f9'

// Browser: subscribe as one actor
const client = NoLag('your_access_token')
await client.connect()

const room = client.setApp(APP_SLUG).setRoom('general')
room.subscribe('message')
room.on('message', (data) => {
  console.log('Received:', data)
})

// Server (or any other authorised actor): publish. Publishers never receive
// their own messages, so publish from a different actor than the one listening.
const server = NoLag('service_access_token')
await server.connect()
server.setApp(APP_SLUG).setRoom('general').emit('message', { text: 'Hello!' })
```

The shape is the same. The main differences are that NoLag groups topics inside a **room** (so you get natural namespacing and presence per room), access is controlled with **actors and tokens** rather than a per-channel auth endpoint, and any authorised actor can publish from the client side, not only your server. A NoLag publisher never hears its own message back, so append it to your UI locally when you send.

## Auth: delete your auth endpoint

Pusher private and presence channels call back to an auth endpoint you host, on every subscribe. NoLag does not need one. A connection authenticates once with an **actor access token**, and what that actor can read or write is enforced at the broker with per-topic ACL, so there is no per-subscribe round-trip to your server. Every credential resolves to a typed actor: `user`, `device`, `service`, `session`, `agent`, `orchestrator` or `observer`.

For browser clients, mint a short-lived JWT ([client token](/docs/client-tokens)) on your backend, signed with a project signing key. No NoLag API call is needed to issue one, and it expires on its own within minutes, so a long-lived secret never reaches the browser.

## Presence

Pusher presence channels map to NoLag [presence](/docs/concepts/presence), tracked per room. A client joins the member list by calling `room.setPresence(data)`, and everyone else gets `presence:join`, `presence:leave` and `presence:update` events on the client, plus `room.fetchPresence()` for the current member list, without standing up your own tracking.

## Next steps

- Follow the [5-minute quick start](/docs/getting-started) to connect for the first time.
- If you are building chat, the [chat blueprint](/docs/high-level-sdks) gives you rooms, who-is-online presence, typing indicators and streamed messages out of the box. It keeps an in-memory cache of the messages it has seen; message history is yours to store.
- Compare the platforms in more detail: [NoLag vs Pusher](/compare/pusher).
