Skip to main content

Room Access Rules

Beta

This feature is in beta. Core behavior is stable and ready to try, but some APIs or configuration may still evolve before general availability.

Rooms use explicit room-level access checks under rooms[namespace].access.

Configuration

import { defineConfig } from '@edge-base/shared';

export default defineConfig({
rooms: {
game: {
access: {
metadata: (auth, roomId) => auth !== null,
join: (auth, roomId) => auth?.custom?.plan === 'pro',
action: (auth, roomId, actionType) => {
if (!auth) return false;
if (actionType === 'BAN_PLAYER') return auth.role === 'admin';
return true;
},
},
handlers: {
lifecycle: {
onJoin(sender, room) {
room.sendMessage('joined', { userId: sender.userId });
},
},
actions: {
MOVE(payload, room, sender) {
room.setPlayerState(sender.userId, (state) => ({
...state,
position: payload,
}));
},
},
},
},
},
});

Release Mode

Rooms are fail-closed in release mode.

  • If public is not enabled
  • and the matching access.* function is missing
  • then metadata, join, and action are denied

Use public only as an explicit opt-in:

rooms: {
lobby: {
public: {
metadata: true,
join: true,
},
},
}

access vs handlers

  • access.metadata
    • decides whether room metadata can be fetched
  • access.join
    • decides whether a player can enter the room
  • access.action
    • decides whether a client action is allowed
  • access.signal
    • decides whether a signal can be sent
  • handlers.lifecycle.onJoin
    • runs after join is accepted
  • handlers.actions
    • handles accepted action payloads
  • handlers.timers
    • handles server timers

Signal Access

Control which signals can be sent inside a room:

rooms: {
game: {
access: {
signal: (auth, roomId, event, payload) => {
if (!auth) return false;
// Only allow known signal types
return ['cursor-move', 'typing', 'reaction'].includes(event);
},
},
},
}
ParameterTypeDescription
authAuthContext | nullThe sender's identity
roomIdstringThe room instance ID
eventstringSignal event name
payloadunknownSignal payload

In release mode, missing signal access rules deny by default, matching the same fail-closed behavior as metadata, join, and action.

Using auth.meta

Room access receives the same enriched auth context as HTTP routes.

export default defineConfig({
auth: {
handlers: {
hooks: {
enrich: async (auth) => ({
workspaceId: await lookupWorkspace(auth.id),
}),
},
},
},
rooms: {
board: {
access: {
join: (auth) => Boolean(auth?.meta?.workspaceId),
},
},
},
});

Room Sender

Lifecycle and action handlers receive a sender object with:

  • userId
  • connectionId
  • role

Use auth inside access. Use sender inside handlers after the join has been accepted.

See Also