# How SimStudio AI / Sim Implements Real-Time Collaborative Canvas with Socket.IO

> Discover how SimStudio AI's real-time collaborative canvas uses Socket.IO to validate, persist, and broadcast workflow operations, scaling efficiently across deployments.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: how-to-guide
- Published: 2026-05-02

---

**SimStudio AI / Sim powers its workflow editor's real-time collaboration through a dedicated Socket.IO server that validates, persists, and broadcasts workflow operations across users, scaling from single-pod to multi-pod deployments via Redis.**

The `simstudioai/sim` repository implements a robust real-time collaborative canvas for its visual workflow editor using a dedicated **Socket.IO server** located in `apps/realtime`. This architecture enables multiple users to simultaneously edit workflows with immediate synchronization of changes, role-based permissions, and automatic persistence to the database.

## Server Bootstrap and Configuration

The collaboration server initializes in [`apps/realtime/src/index.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/index.ts) by creating an HTTP server and attaching a configured Socket.IO instance.

```typescript
const httpServer = createServer()
const io = await createSocketIOServer(httpServer)   // ← config in config/socket.ts
io.use(authenticateSocket)                         // ← auth middleware
io.on('connection', (socket) => setupAllHandlers(socket, roomManager))

```

The `createSocketIOServer` function in [`apps/realtime/src/config/socket.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/config/socket.ts) handles CORS configuration, ping intervals, and critical scaling logic. When the `REDIS_URL` environment variable is present, the server creates a **Redis adapter** using `@socket.io/redis-adapter` to enable multi-pod broadcasting.

```typescript
const io = new Server(httpServer, { cors: …, pingTimeout: … })
if (env.REDIS_URL) {
  const pub = createClient(redisOptions)
  const sub = createClient(redisOptions)
  await Promise.all([pub.connect(), sub.connect()])
  io.adapter(createAdapter(pub, sub))
}

```

This configuration allows the system to run in **single-pod mode** (using in-memory `MemoryRoomManager`) or **multi-pod mode** (using `RedisRoomManager`) without code changes, driven entirely by the presence of Redis configuration.

## Authentication and Room Management

Before accessing collaborative features, every socket must pass through the authentication middleware in [`apps/realtime/src/middleware/auth.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/middleware/auth.ts). This validates the session cookie using the shared Better-Auth secret and decorates the socket with a typed `AuthenticatedSocket` containing `userId`, `userName`, and other user metadata.

Room management is abstracted through [`apps/realtime/src/rooms/memory-manager.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/rooms/memory-manager.ts) and [`apps/realtime/src/rooms/redis-manager.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/rooms/redis-manager.ts). These managers maintain mappings between sockets and workflow rooms, track per-user presence (including roles and last activity), and expose helper methods such as:

- `getWorkflowIdForSocket`
- `getUserSession`
- `hasWorkflowRoom`
- `updateUserActivity`

## Operation Handling and Broadcasting

The core collaboration logic resides in [`apps/realtime/src/handlers/operations.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/operations.ts). When a client emits a `workflow-operation` event, the handler executes a strict validation and authorization pipeline:

1. **Validation**: The payload is validated against `WorkflowOperationSchema` using Zod
2. **Authorization**: The handler checks user role permissions via `checkRolePermission` and enforces workflow mutability with `assertWorkflowMutable`
3. **Persistence vs. Broadcasting**: The system distinguishes between high-frequency position updates and critical state changes
   - **Position updates** (block moves) are **broadcast first** to minimize latency, with optional persistence if marked as committed
   - **Other operations** are **persisted first** via `persistWorkflowOperation`, then broadcast to room members

After successful persistence, the server broadcasts changes to all other sockets in the workflow room:

```typescript
socket.to(workflowId).emit('workflow-operation', broadcastData)
socket.emit('operation-confirmed', { operationId, serverTimestamp: Date.now() })

```

On failure, the originating client receives `operation-failed` or `operation-error` with detailed debugging information.

## Client-Side Integration

Clients connect using standard Socket.IO client libraries. The following React hook demonstrates connecting to the collaborative canvas, joining a workflow room, and handling remote operations:

```typescript
import { io, Socket } from 'socket.io-client'
import { useEffect, useRef } from 'react'

export function useCollaborativeWorkflow(workflowId: string) {
  const socketRef = useRef<Socket | null>(null)

  useEffect(() => {
    const socket = io(import.meta.env.VITE_SOCKET_URL, {
      withCredentials: true,
      transports: ['websocket'],
    })
    socketRef.current = socket

    // Join the workflow room
    socket.emit('join-workflow', { workflowId })

    // Listen for remote operations
    socket.on('workflow-operation', (data) => {
      // Apply the operation to the local state (e.g. via Zustand or React-Query)
      applyRemoteOperation(data)
    })

    // Cleanup on unmount
    return () => {
      socket.disconnect()
    }
  }, [workflowId])

  // Send a local operation
  const sendOperation = (op) => {
    socketRef.current?.emit('workflow-operation', op)
  }

  return { sendOperation }
}

```

## Presence and Graceful Shutdown

Additional handlers in [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts) and [`apps/realtime/src/handlers/subblocks.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/subblocks.ts) manage specialized updates for workflow variables and subblocks. The system tracks user presence and automatically cleans up pending operation IDs when sockets disconnect to prevent memory leaks.

For graceful shutdown, [`apps/realtime/src/config/socket.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/config/socket.ts) exports `shutdownSocketIOAdapter` to close Redis connections, while [`apps/realtime/src/index.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/index.ts) implements a top-level shutdown routine:

```typescript
const shutdown = async () => {
  await roomManager.shutdown()
  await shutdownSocketIOAdapter()
  httpServer.close(() => process.exit(0))
}
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)

```

## Summary

- **Socket.IO server architecture**: The `apps/realtime` package runs as a dedicated service with configurable Redis adapter support for horizontal scaling
- **Strict validation pipeline**: Every operation passes through Zod schema validation, role-based permission checks, and mutability assertions before processing
- **Optimized broadcasting**: Position updates broadcast immediately for responsiveness, while state-changing operations persist to database before broadcasting to ensure durability
- **Room abstraction**: The `RoomManager` interface (with Memory and Redis implementations) handles socket-to-workflow mappings and user presence across single or multi-pod deployments
- **Type-safe authentication**: Middleware enforces session validation using Better-Auth and decorates sockets with typed user metadata for secure collaboration

## Frequently Asked Questions

### How does SimStudio AI handle conflicts when multiple users edit simultaneously?

The system processes operations sequentially on the server. Each `workflow-operation` is validated against `WorkflowOperationSchema` and authorized via `checkRolePermission` before being persisted. For high-frequency updates like dragging blocks, the server broadcasts position changes immediately to all clients in the room via `socket.to(workflowId).emit()`, ensuring all participants see live cursor movements and block repositioning in real-time.

### Can the real-time collaboration scale across multiple server instances?

Yes. When `REDIS_URL` is configured, [`apps/realtime/src/config/socket.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/config/socket.ts) initializes a Redis adapter using `@socket.io/redis-adapter` with separate publisher and subscriber clients. This allows Socket.IO to broadcast messages across all pods using the `RedisRoomManager` implementation, while single-pod deployments use the `MemoryRoomManager` without code changes.

### What happens if a user loses connection during an edit?

The room manager tracks user sessions and activity timestamps through `updateUserActivity`. When a socket disconnects, cleanup handlers remove pending operation IDs and update room state. Upon reconnection, the client rejoins the workflow room via `join-workflow` and receives the current state, with any missed operations handled through the standard acknowledgment pattern (`operation-confirmed` or `operation-failed`).

### How are permissions enforced in the collaborative canvas?

The `authenticateSocket` middleware in [`apps/realtime/src/middleware/auth.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/middleware/auth.ts) validates the session cookie and attaches user metadata to the socket. Before executing any operation in [`apps/realtime/src/handlers/operations.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/operations.ts), the system calls `checkRolePermission` to verify the user has appropriate access rights (e.g., editor vs. viewer) and `assertWorkflowMutable` to ensure the workflow allows modifications. Unauthorized operations are rejected before reaching the persistence layer.