The $io class is the main Socket.IO instance available globally on the Strapi object (strapi.$io).
Version: 5.x for Strapi v5
TypeScript: Full type definitions included
// Access the Socket.IO instance
const io = strapi.$io;
// Core methods
io.emit({ event, schema, data });
io.raw({ event, rooms, data });
// Room management
io.joinRoom(socketId, roomName);
io.leaveRoom(socketId, roomName);
io.getSocketsInRoom(roomName);
// Messaging
io.sendPrivateMessage(socketId, event, data);
io.broadcast(socketId, event, data);
io.emitToNamespace(namespace, event, data);
// Connection management
io.disconnectSocket(socketId, reason);
// Entity subscriptions (new in v5.0)
io.subscribeToEntity(socketId, uid, id);
io.unsubscribeFromEntity(socketId, uid, id);
io.getEntitySubscriptions(socketId);
io.emitToEntity(uid, id, event, data);
io.getEntityRoomSockets(uid, id);
// Properties
io.server; // Raw Socket.IO server
io.namespaces; // All namespacesDirect access to the Socket.IO server instance.
Type: Server from socket.io
// Get all connected sockets
const sockets = strapi.$io.server.sockets.sockets;
console.log(`Connected clients: ${sockets.size}`);
// Broadcast to everyone
strapi.$io.server.emit('announcement', {
message: 'Server maintenance in 10 minutes'
});
// Access adapter and rooms
const rooms = strapi.$io.server.sockets.adapter.rooms;
console.log('Active rooms:', Array.from(rooms.keys()));Access all configured namespaces.
Type: Record<string, Namespace>
// Emit to specific namespace
if (strapi.$io.namespaces.admin) {
strapi.$io.namespaces.admin.emit('dashboard:update', {
users: 1234,
activeNow: 56
});
}
// List all namespaces
Object.keys(strapi.$io.namespaces).forEach(ns => {
console.log(`Namespace: ${ns}`);
});Sends data to all roles/tokens with permission for the content type action. Automatically sanitizes and transforms data based on role permissions.
Signature:
emit(options: EmitOptions): Promise<void>
interface EmitOptions {
event: string; // Action: 'create' | 'update' | 'delete' | custom
schema: object; // Content type schema
data: any; // Data to emit
}Supported Actions: create, update, delete (or custom actions)
Event Format: {contentType}:{action} (e.g., article:create)
Features:
- ✅ Automatic permission checking
- ✅ Data sanitization per role
- ✅ Relation population (if configured)
- ✅ Transform output
::: code-group
// Emit article creation event
strapi.$io.emit({
event: 'create',
schema: 'api::article.article',
data: articleData
});
// Only users with 'create' permission on articles receive this// Emit custom event with schema
strapi.$io.emit({
event: 'publish',
schema: 'api::article.article',
data: { id: 123, status: 'published' }
});
// Receives as: socket.on('article:publish', ...)// In your controller
async create(ctx) {
const entry = await strapi.entityService.create(
'api::article.article',
{ data: ctx.request.body }
);
// Emit to all permitted users
await strapi.$io.emit({
event: 'create',
schema: 'api::article.article',
data: entry
});
return entry;
}:::
Emit events without permission checks or data sanitization. Use for custom events that don't relate to content types.
Signature:
raw(options: RawEmitOptions): Promise<void>
interface RawEmitOptions {
event: string; // Any event name
rooms?: string[]; // Optional: specific rooms
data: any; // Data to emit (not sanitized!)
}::: warning Data is NOT sanitized or transformed. Only send safe, pre-validated data! :::
::: code-group
// Send to all connected clients
strapi.$io.raw({
event: 'notification',
data: {
type: 'info',
message: 'System maintenance completed'
}
});// Send only to premium users
strapi.$io.raw({
event: 'exclusive-offer',
rooms: ['premium', 'vip'],
data: {
discount: 50,
expiresIn: '24h'
}
});// Send custom analytics event
strapi.$io.raw({
event: 'analytics:pageview',
data: {
page: '/products',
timestamp: Date.now(),
visitors: 42
}
});:::
Add a socket to a room.
Signature:
joinRoom(socketId: string, roomName: string): booleanReturns: true if successful, false if socket not found
// Add user to premium room after purchase
strapi.$io.joinRoom(socket.id, 'premium-users');
// Add to multiple rooms
['notifications', 'chat', 'updates'].forEach(room => {
strapi.$io.joinRoom(socket.id, room);
});Remove a socket from a room.
Signature:
leaveRoom(socketId: string, roomName: string): booleanReturns: true if successful, false if socket not found
// Remove from room on subscription end
strapi.$io.leaveRoom(socket.id, 'premium-users');
// Leave all custom rooms
const socket = strapi.$io.server.sockets.sockets.get(socketId);
if (socket) {
socket.rooms.forEach(room => {
if (room !== socket.id) { // Don't leave own room
strapi.$io.leaveRoom(socket.id, room);
}
});
}Get all sockets in a specific room.
Signature:
getSocketsInRoom(roomName: string): Promise<SocketInfo[]>
interface SocketInfo {
id: string;
userId?: number;
role?: string;
rooms: string[];
}// Check who's in a chat room
const sockets = await strapi.$io.getSocketsInRoom('chat-lobby');
console.log(`${sockets.length} users in lobby`);
sockets.forEach(socket => {
console.log(`Socket ${socket.id}, User: ${socket.userId}`);
});
// Check if room is empty before closing it
if (sockets.length === 0) {
console.log('Chat room is empty, closing...');
}Send a message to a specific socket only.
Signature:
sendPrivateMessage(
socketId: string,
event: string,
data: any
): void// Send private notification
strapi.$io.sendPrivateMessage(
socket.id,
'private-notification',
{
type: 'success',
message: 'Your payment was processed'
}
);
// Send user-specific data
strapi.$io.sendPrivateMessage(
socket.id,
'user-stats',
{
points: 1250,
level: 15,
achievements: ['first-post', 'verified']
}
);Emit to all sockets except the sender.
Signature:
broadcast(
socketId: string,
event: string,
data: any
): voidUse Case: Notify others about a user's action without notifying the user themselves.
// User joined - notify others
strapi.$io.broadcast(socket.id, 'user-joined', {
username: 'john_doe',
avatar: '/avatars/john.png'
});
// User typing indicator
strapi.$io.broadcast(socket.id, 'typing', {
userId: 123,
chatRoom: 'general'
});
// Collaborative editing - sync changes to others
strapi.$io.broadcast(socket.id, 'document:update', {
documentId: 456,
changes: [{ op: 'insert', pos: 10, text: 'Hello' }]
});Emit event to all sockets in a specific namespace.
Signature:
emitToNamespace(
namespace: string,
event: string,
data: any
): void// Update admin dashboard
strapi.$io.emitToNamespace('admin', 'dashboard:stats', {
onlineUsers: 1234,
revenue: 56789,
orders: 42
});
// Send to chat namespace
strapi.$io.emitToNamespace('chat', 'announcement', {
message: 'New features available!'
});Forcefully disconnect a socket with optional reason.
Signature:
disconnectSocket(
socketId: string,
reason?: string
): booleanReturns: true if socket was found and disconnected
// Kick abusive user
strapi.$io.disconnectSocket(socket.id, 'Violation of terms of service');
// Disconnect on account deletion
strapi.$io.disconnectSocket(socket.id, 'Account deleted');
// Session timeout
strapi.$io.disconnectSocket(socket.id, 'Session expired');Entity subscriptions allow targeted updates for specific entities rather than receiving all events for a content type.
Subscribe a socket to a specific entity (server-side).
Signature:
subscribeToEntity(
socketId: string,
uid: string,
id: string | number
): Promise<EntitySubscriptionResult>
interface EntitySubscriptionResult {
success: boolean;
room?: string;
uid?: string;
id?: string | number;
error?: string;
}// Subscribe user to article updates
const result = await strapi.$io.subscribeToEntity(
socket.id,
'api::article.article',
123
);
if (result.success) {
console.log(`Subscribed to room: ${result.room}`);
}Unsubscribe a socket from a specific entity.
Signature:
unsubscribeFromEntity(
socketId: string,
uid: string,
id: string | number
): EntitySubscriptionResult// Unsubscribe when user leaves article page
const result = strapi.$io.unsubscribeFromEntity(
socket.id,
'api::article.article',
123
);Get all entity subscriptions for a socket.
Signature:
getEntitySubscriptions(socketId: string): {
success: boolean;
subscriptions?: Array<{ uid: string; id: string; room: string }>;
error?: string;
}// Check what entities a user is watching
const result = strapi.$io.getEntitySubscriptions(socket.id);
if (result.success) {
result.subscriptions.forEach(sub => {
console.log(`Watching: ${sub.uid} #${sub.id}`);
});
}Emit an event to all clients subscribed to a specific entity.
Signature:
emitToEntity(
uid: string,
id: string | number,
event: string,
data: any
): void// Notify all subscribers when article gets a new comment
strapi.$io.emitToEntity(
'api::article.article',
123,
'article:commented',
{
commentId: 456,
author: 'jane_doe',
text: 'Great article!'
}
);Get all sockets subscribed to a specific entity.
Signature:
getEntityRoomSockets(
uid: string,
id: string | number
): Promise<Array<{ id: string; user: any }>>// Check who's watching an article
const sockets = await strapi.$io.getEntityRoomSockets(
'api::article.article',
123
);
console.log(`${sockets.length} users watching this article`);
sockets.forEach(s => {
console.log(`User: ${s.user?.username || 'anonymous'}`);
});All events follow a consistent format for easy handling.
// Content type events (from emit())
socket.on('article:create', (data) => {
// data is sanitized based on user permissions
console.log(data);
});
// Custom events (from raw())
socket.on('notification', (data) => {
// data is exactly what was sent
console.log(data);
});// Emit custom event from client
socket.emit('custom-event', { foo: 'bar' });
// Listen on server (config in plugins.js)
events: [
{
name: 'custom-event',
handler({ strapi, io }, socket, data) {
console.log('Received:', data);
}
}
]Full TypeScript definitions are included:
import type { SocketIO, EmitOptions, RawEmitOptions } from '@strapi-community/plugin-io/types';
// Access with IntelliSense
const io: SocketIO = strapi.$io;
// Type-safe emit
const options: EmitOptions = {
event: 'create',
schema: 'api::article.article',
data: articleData
};
await io.emit(options);// ✅ Good - automatic permissions
strapi.$io.emit({
event: 'update',
schema: 'api::article.article',
data: updatedArticle
});
// ❌ Bad - no permission checking
strapi.$io.raw({
event: 'article:update',
data: updatedArticle
});// ✅ Good - doesn't relate to content type
strapi.$io.raw({
event: 'server:status',
data: { uptime: process.uptime() }
});// Remove from room when done
socket.on('disconnect', () => {
strapi.$io.leaveRoom(socket.id, 'temporary-room');
});// Before sending expensive data
const sockets = await strapi.$io.getSocketsInRoom('dashboard');
if (sockets.length > 0) {
// Only calculate if someone is watching
const stats = await calculateExpensiveStats();
strapi.$io.raw({
event: 'stats:update',
rooms: ['dashboard'],
data: stats
});
}- Plugin Configuration - Configure events, hooks, and more
- Examples - Real-world implementation patterns
- Helper Functions Guide - Detailed usage examples