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
publicis not enabled - and the matching
access.*function is missing - then
metadata,join, andactionare 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);
},
},
},
}
| Parameter | Type | Description |
|---|---|---|
auth | AuthContext | null | The sender's identity |
roomId | string | The room instance ID |
event | string | Signal event name |
payload | unknown | Signal 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:
userIdconnectionIdrole
Use auth inside access. Use sender inside handlers after the join has been accepted.