Presence
Presence channels let you track which users are online in real-time. Perfect for showing online indicators, collaborative features, and live user lists.
Presence channels let you track which users are online in real-time. Perfect for showing online indicators, collaborative features, and live user lists.
How it works
- Clients subscribe to a presence channel
- User metadata (like
display_name) from signup is automatically included in presence - All subscribers receive join, leave, and sync events
- When a client disconnects, they're automatically removed
Basic usage
Track online users
import { VolcanoAuth } from '@volcano.dev/sdk';
import { VolcanoRealtime } from '@volcano.dev/sdk/realtime';
// 1. Sign up with metadata
const volcano = new VolcanoAuth({ apiUrl, anonKey });
const { session } = await volcano.auth.signUpAnonymous({
display_name: 'Alice', // This will appear in presence events
avatar_url: 'https://example.com/alice.jpg'
});
// 2. Connect to realtime
const realtime = new VolcanoRealtime({
apiUrl,
anonKey,
accessToken: session.access_token
});
await realtime.connect();
// 3. Subscribe to presence channel
const channel = realtime.channel('lobby', { type: 'presence' });
// Listen for users joining
channel.on('join', (info) => {
const username = info.connInfo?.user_metadata?.display_name || 'Anonymous';
console.log('User joined:', username);
});
// Listen for users leaving
channel.on('leave', (info) => {
console.log('User left');
});
// Listen for presence state updates
channel.on('presence_sync', () => {
const state = channel.getPresenceState();
const users = Object.values(state).map(info => ({
id: info.client,
name: info.connInfo?.user_metadata?.display_name || 'Anonymous',
email: info.connInfo?.email
}));
console.log('Online users:', users);
});
await channel.subscribe();
// Note: track() is called automatically - presence comes from user metadata
await channel.track();Get current presence state
// Get all users in the channel
const state = channel.getPresenceState();
// Returns an object like:
// {
// 'client-123': {
// user: 'user-id-1',
// client: 'client-123',
// connInfo: {
// user_id: 'user-id-1',
// email: 'alice@example.com',
// user_metadata: {
// display_name: 'Alice',
// avatar_url: 'https://...'
// }
// }
// }
// }
// Extract user list
const users = Object.values(state).map(info => ({
id: info.client,
name: info.connInfo?.user_metadata?.display_name,
email: info.connInfo?.email
}));
console.log(`${users.length} users online`);User Metadata in Presence
Presence information comes from user metadata set during signup:
// Anonymous signup with metadata
const { session } = await volcano.auth.signUpAnonymous({
display_name: 'Alice',
avatar_url: 'https://example.com/alice.jpg',
status: 'online'
});
// This metadata is automatically included in presence events
// Other users will see it in connInfo.user_metadataExample: Online users list with names
function OnlineUsers({ roomId, anonKey, accessToken }) {
const [users, setUsers] = useState([]);
useEffect(() => {
const realtime = new VolcanoRealtime({ apiUrl, anonKey, accessToken });
async function setup() {
await realtime.connect();
const channel = realtime.channel(`room-${roomId}`, { type: 'presence' });
// Update user list on any presence change
channel.on('presence_sync', () => {
const state = channel.getPresenceState();
const userList = Object.values(state).map(info => ({
id: info.client,
name: info.connInfo?.user_metadata?.display_name || 'Anonymous',
avatar: info.connInfo?.user_metadata?.avatar_url
}));
setUsers(userList);
});
await channel.subscribe();
await channel.track(); // Automatic - uses metadata from signup
}
setup();
return () => realtime.disconnect();
}, [roomId]);
return (
<div className="online-users">
<h3>Online ({users.length})</h3>
<ul>
{users.map(user => (
<li key={user.id}>
{user.avatar && <img src={user.avatar} alt={user.name} />}
<span>{user.name}</span>
</li>
))}
</ul>
</div>
);
}Example: Typing indicator
// For typing indicators, use broadcast messages instead of presence
// Presence is best for "who's here", broadcast for "what they're doing"
const chatChannel = realtime.channel('chat-room');
chatChannel.on('message', (msg) => {
if (msg.type === 'typing') {
console.log(`${msg.username} is typing...`);
}
});
// User starts typing
await chatChannel.send({
type: 'typing',
username: currentUser.name,
isTyping: true
});
// User stops typing
await chatChannel.send({
type: 'typing',
username: currentUser.name,
isTyping: false
});Channel naming
Use simple, descriptive names - project isolation is automatic:
// Good - simple and descriptive
const lobby = realtime.channel('lobby', { type: 'presence' });
const room = realtime.channel('room-123', { type: 'presence' });
const dashboard = realtime.channel('dashboard-view', { type: 'presence' });Presence channel names can be up to 64 characters.
What's included in presence
Automatic fields (from server):
client- Unique client connection IDuser- User ID from authenticationconnInfo.user_id- User IDconnInfo.email- User emailconnInfo.user_metadata- Custom data from signup (display_name, avatar_url, etc.)
Note: Email and user_id are always included. Set user metadata during signup to customize what appears in presence.
Plan limits
Presence limits are based on your project plan:
| Limit | FREE | PRO |
|---|---|---|
| Max channels per connection | 100 | 200 |
| Max presences per channel | 10,000 | 10,000 |
| Message size (for broadcast) | Plan-based | Plan-based |
Best practices
-
Set user metadata during signup: Include
display_name,avatar_url, etc.await volcano.auth.signUpAnonymous({ display_name: 'Alice', avatar_url: 'https://example.com/alice.jpg' }); -
Use appropriate channels: Create separate presence channels for different contexts
-
Handle multiple connections: Users might have multiple tabs/devices - use
clientID to differentiate -
Clean up on unmount: Always unsubscribe when components unmount
-
Use broadcast for actions: Presence = "who's here", Broadcast = "what they're doing"
See also
- Broadcast Channels - For chat messages and actions
- JavaScript SDK - SDK reference
- Security - Authentication and isolation