# Pub/Sub

> Publish named events to channels. All subscribers receive them instantly. No server code required for basic pub/sub on Cloudflare Workers.

Publish custom named events to a channel. All subscribers receive them in real time. No server code required for basic pub/sub.

## Channel naming

Use a **path with a slash** for custom channels. This bypasses ChangeTracker and uses pure WebSocket pub/sub:

```ts
const channel = realtime.channel('todos/shared-workspace')
// or:
const channel = realtime.channel('chat/general')
const channel = realtime.channel('game/room-42')
```

## Publish an event

```ts
channel.publish('todo:created', {
  id: '123',
  text: 'Buy groceries',
  done: false,
})
```

All other subscribers on `todos/shared-workspace` receive this event immediately.

## Listen for events

```ts
channel
  .on('todo:created', ({ data }) => {
    setTodos(prev => [...prev, data])
  })
  .on('todo:toggled', ({ data }) => {
    setTodos(prev => prev.map(t => t.id === data.id ? data : t))
  })
  .on('todo:deleted', ({ data }) => {
    setTodos(prev => prev.filter(t => t.id !== data.id))
  })
  .subscribe()
```

## Avoid echo on your own events

Use a client ID to skip events you published yourself:

```tsx
const clientId = useRef(Math.random().toString(36).slice(2))

channel.on('todo:created', ({ data, userId }) => {
  if (userId === clientId.current) return // skip own event
  setTodos(prev => [...prev, data])
})

// Pass your clientId so the server includes it in userId
channel.publish('todo:created', todo)
```

## Persist events for history

Pass `{ persist: true }` to store the message in the database for later retrieval:

```ts
channel.publish('message', {
  text: 'Hello everyone!',
  userId: user.id,
}, { persist: true })
```

Retrieve history with `channel.getHistory()` — see [Message History](/features/realtime/history).

## Server-side pub/sub

Your Workers/Node.js server can also publish to channels:

```ts
// In your Worker handler
sdk.socket.emit('todo:created', newTodo, 'todos/shared-workspace')
```

This is useful when you want to validate or transform data before broadcasting.

## Complete example

```tsx

function CollaborativeList() {
  const { realtime } = useAerostack()
  const [items, setItems] = useState([])
  const myId = useRef(Math.random().toString(36).slice(2))
  const channelRef = useRef(null)

  useEffect(() => {
    const ch = realtime.channel('list/shared')
    channelRef.current = ch

    ch
      .on('item:added', ({ data, userId }) => {
        if (userId === myId.current) return
        setItems(prev => [...prev, data])
      })
      .subscribe()

    return () => ch.unsubscribe()
  }, [realtime])

  const addItem = (text) => {
    const item = { id: Date.now(), text }
    setItems(prev => [...prev, item])                    // optimistic
    channelRef.current.publish('item:added', item)       // broadcast to peers
  }

  return (
    <>
      <button onClick={() => addItem('New item')}>Add</button>
      <ul>{items.map(i => <li key={i.id}>{i.text}</li>)}</ul>
    </>
  )
}
```

See the [Collaborative Todos example](/examples/collaborative-todos) for a complete working implementation with toggle, delete, and presence.
