LogoRobo.js

Multiplayer

Add real-time multiplayer features to your Discord Activity

@robojs/sync provides real-time state synchronization for Discord Activities. It syncs state across all connected clients using WebSockets.

Setup

Install the plugin:

npx robo add @robojs/sync@next

Wrap your app in SyncContextProvider. This requires @robojs/server, which is already included in scaffolded activity projects.

src/app/App.tsx
import { SyncContextProvider } from '@robojs/sync'

export function App() {
  return (
    <SyncContextProvider>
      <Activity />
    </SyncContextProvider>
  )
}
src/app/App.jsx
import { SyncContextProvider } from '@robojs/sync'

export function App() {
  return (
    <SyncContextProvider>
      <Activity />
    </SyncContextProvider>
  )
}

Shared State

useSyncState works like React's useState but syncs across all connected clients. Think of keys like channel names — components using the same key share the same state.

src/app/Counter.tsx
import { useSyncState } from '@robojs/sync'

function Counter() {
  const [count, setCount] = useSyncState(0, ['counter'])

  return (
    <button onClick={() => setCount((prev) => prev + 1)}>
      Count: {count}
    </button>
  )
}
src/app/Counter.jsx
import { useSyncState } from '@robojs/sync'

function Counter() {
  const [count, setCount] = useSyncState(0, ['counter'])

  return (
    <button onClick={() => setCount((prev) => prev + 1)}>
      Count: {count}
    </button>
  )
}

Two browser windows side by side showing the same activity with a synchronized counter, where clicking in one window updates both

FocusTwo windows showing the same counter value in real-time syncZoom100%NotesShow two browser windows running the same activity. Both display 'Count: 5' button. The key message: both show the same value because useSyncState synchronizes across clients.

Key Arrays

Keys determine which clients share state. Different key structures create different scopes.

KeyScope
['counter']All clients share one counter
['player', userId]Per-user state
['room', roomId, 'chat']Per-room chat

Updating State

Use updater functions for concurrent safety:

src/app/Counter.tsx
setCount((prev) => prev + 1)
src/app/Counter.jsx
setCount((prev) => prev + 1)

Partial updates merge with existing state for objects. For example, updating only the x field leaves y unchanged:

src/app/Game.tsx
const [position, setPosition] = useSyncState({ x: 0, y: 0 }, ['position'])
setPosition({ x: 100 }) // y remains 0
src/app/Game.jsx
const [position, setPosition] = useSyncState({ x: 0, y: 0 }, ['position'])
setPosition({ x: 100 }) // y remains 0

Client Awareness

The third return value from useSyncState is a context object with information about connected clients.

src/app/Activity.tsx
const [state, setState, context] = useSyncState(initialState, ['room'])
src/app/Activity.jsx
const [state, setState, context] = useSyncState(initialState, ['room'])
FieldTypeDescription
clientsClient[]All connected clients
clientIdstringCurrent client's ID
isHostbooleanWhether current client is host
broadcastfunctionSend ephemeral message to all clients
sendfunctionSend ephemeral message to a specific client

A Discord Activity showing connected users with a participant count and host indicator, demonstrating client awareness information

FocusThe client awareness UI showing participants and host indicatorZoom100%NotesShow an activity displaying: list of connected users, count like '3 players connected', and host indicator (crown or 'Host' label). Demonstrates context object.

Colyseus

For most activities, @robojs/sync is sufficient. Consider Colyseus for complex games needing server-authoritative state — where the server is the single source of truth and validates every action, preventing cheating.

Start from a template:

npx create-robo@next my-activity --template discord-activities/react-colyseus-ts

Colyseus is a dedicated multiplayer game server with schema-based state, room management, and reconnection support. See the Colyseus documentation for details.

Choosing a Solution

@robojs/syncColyseus
SetupOne commandManual configuration
Server logicNone requiredRoom-based handlers
State modelLast-write-wins (the most recent update from any client overwrites previous values)Authoritative server (the server validates and controls all state changes)
PersistenceIn-memoryConfigurable
Best forPrototypes, simple syncComplex games, scaling

A multiplayer activity inside Discord with multiple users interacting simultaneously, showing real-time state updates like shared cursors or a collaborative interface

FocusMultiple users interacting in real-time within the same activityZoom100%NotesShow an activity in Discord's embedded panel with visual evidence of multiplayer: multiple cursors, shared drawing, or game board. Include Discord voice channel UI for context.

Next Steps

On this page