# How Zed Real-Time Collaborative Editing Works: A Deep Dive into the Collab Server Architecture

> Discover how Zed's real-time collaborative editing works. Explore the Collab server architecture, WebSocket connections, and efficient buffer updates for a seamless multi-user experience.

- Repository: [Zed Industries/zed](https://github.com/zed-industries/zed)
- Tags: deep-dive
- Published: 2026-03-01

---

**Zed's real-time collaborative editing uses a dedicated Collab server that manages WebSocket connections, routes protobuf RPC messages, and broadcasts buffer updates through per-project connection pools to enable low-latency, multi-user editing sessions.**

Zed is a Rust-based code editor built for speed and collaboration at its core. Its real-time collaborative editing capabilities rely on a sophisticated client-server architecture that synchronizes buffer states across multiple users with minimal latency. This article examines the internal mechanics of Zed's Collab server, from WebSocket handshake to buffer synchronization, based on the actual implementation in the `zed-industries/zed` repository.

## WebSocket Connection Layer and Protocol Versioning

Every collaborative session begins with a WebSocket upgrade request to the `/rpc` endpoint. In [`crates/collab/src/rpc.rs`](https://github.com/zed-industries/zed/blob/main/crates/collab/src/rpc.rs), the `handle_websocket_request` function (lines 84-90) validates incoming connection attempts by inspecting mandatory version headers, including `x-zed-protocol-version` and `x-zed-app-version`. The server rejects connections with mismatched protocol versions to ensure compatibility between clients and the Collab service.

To protect server resources, Zed implements connection gating through `ConnectionGuard::try_acquire` (lines 95-106). A global atomic counter enforces `MAX_CONCURRENT_CONNECTIONS`; when the limit is reached, the server immediately responds with **503 Service Unavailable** rather than accepting the socket and overloading the system.

## Session Management and Initial Synchronization

Once the WebSocket is accepted, `Server::handle_connection` instantiates a `Session` struct (lines 182-199) that maintains all state for the client connection. The Session encapsulates:

- The authenticated `Principal` representing the user identity
- A unique `ConnectionId` for socket addressing
- A database handle for persistent storage
- A reference to the shared `Peer` message router
- A per-project `ConnectionPool` for efficient broadcast targeting
- The global `AppState` for shared server resources

Immediately after establishment, the server sends an initial synchronization payload via `send_initial_client_update` (lines 306-340). This includes a `proto::Hello` message, the user's contact list, pending incoming calls, and the current list of collaborators for every project the user has joined.

## RPC Message Routing with Protocol Buffers

Zed's real-time collaboration uses a binary RPC protocol built on Protocol Buffers. Every message is an **enveloped protobuf** (`proto::*`) transmitted over the WebSocket. The server core maintains a dispatch system where handlers are registered in `Server::new` via `add_request_handler` and `add_message_handler` (lines 79-89).

The dispatcher stores registered callbacks in a `handlers: HashMap<TypeId, MessageHandler>`. When a message arrives, the server looks up the appropriate handler using `payload_type_id()` and executes the corresponding async function. This type-safe routing enables the server to handle distinct operations—such as joining projects, updating buffers, or managing voice calls—through a unified transport layer.

## Project Sharing and Buffer Synchronization

The entry point for collaboration is the `ShareProject` request. When a user shares a project, the client sends `proto::ShareProject { project_id, ... }` to the server. The handler (lines 1766-1782) records the user as a participant, updates the database, and broadcasts a `ShareProjectResponse` containing the current buffer state and collaborator metadata to all connected clients.

Once sharing is active, every keystroke generates an `UpdateBuffer` protobuf message. The server forwards these updates to all other participants in the same project using the `ConnectionPool` abstraction. The pool maintains `connection_ids` indexed by both user and project, enabling the `broadcast` helper (lines 884-896) to efficiently fan-out messages without iterating through unrelated sessions.

The following pattern illustrates how the server handles buffer updates:

```rust
// Conceptual implementation based on crates/collab/src/rpc.rs patterns
async fn update_buffer(
    request: proto::UpdateBuffer,
    response: Response<proto::UpdateBuffer>,
    ctx: MessageContext,
) -> Result<()> {
    // Retrieve other participants in this project
    let pool = ctx.session.connection_pool().await?;
    let other_ids = pool.connections_for_project(request.project_id);
    
    // Broadcast the edit to all other connections
    broadcast(
        Some(ctx.session.connection_id),
        other_ids,
        |conn_id| {
            ctx.session.peer.send(
                conn_id,
                proto::UpdateBuffer {
                    ..request.clone()
                },
            )
        },
    );
    
    // Acknowledge the originating client
    response.send(proto::UpdateBufferResponse {})
}

```

## Concurrency Control and Fault Tolerance

To prevent head-of-line blocking, the Collab server strictly separates message handling into foreground and background paths. Foreground handlers—those requiring a response—are constrained by `MAX_CONCURRENT_HANDLERS` and execute within a `FuturesUnordered` pool (lines 231-250). Background notifications, such as `UpdateBuffer` or LiveKit voice events, are detached onto the executor immediately, ensuring that a slow request cannot stall the message pump for other users.

When a client disconnects, the server does not immediately purge the session. Instead, it waits for a **30-second reconnect timeout** before invoking `connection_lost` (lines 1212-1230). This grace period allows clients to resume their session during transient network interruptions. Once the timeout expires, the server clears the user's presence from all rooms, channel buffers, and projects, notifying remaining participants of the departure.

## Client-Side UI Integration

The `collab_ui` crate consumes the RPC stream to render collaborative features. In [`crates/collab_ui/src/collab_panel.rs`](https://github.com/zed-industries/zed/blob/main/crates/collab_ui/src/collab_panel.rs) (lines 152-158), the UI registers the `ShareProject` action, which triggers the initial share request when a user clicks the collaboration button. The panel also displays the list of active collaborators fetched from the initial sync.

Additionally, [`collab_notification.rs`](https://github.com/zed-industries/zed/blob/main/collab_notification.rs) handles presence indicators, showing remote cursor locations and audio mute status for users connected via LiveKit integration. The UI forwards local edits to the server through the RPC client, which serializes them into `UpdateBuffer` messages for transmission.

## Summary

Zed's real-time collaborative editing architecture demonstrates several key engineering principles:

- **WebSocket-based RPC** with Protocol Buffers provides binary-efficient, low-latency communication between clients and the Collab server
- **Connection pooling** via `ConnectionPool` enables O(1) broadcast of buffer updates to project participants without scanning all active sessions
- **Strict concurrency limits** (`MAX_CONCURRENT_CONNECTIONS` and `MAX_CONCURRENT_HANDLERS`) protect the server from resource exhaustion during traffic spikes
- **Graceful degradation** through a 30-second reconnect window allows users to recover from temporary disconnections without losing context
- **Type-safe message routing** using Rust's `TypeId` system ensures that RPC handlers are dispatched correctly at runtime

## Frequently Asked Questions

### How does Zed's Collab server handle concurrent edits to the same file?

The server treats each `UpdateBuffer` message as an atomic broadcast event. When a client sends an edit, the `broadcast` helper in [`crates/collab/src/rpc.rs`](https://github.com/zed-industries/zed/blob/main/crates/collab/src/rpc.rs) (lines 884-896) forwards the protobuf message to all other `ConnectionId` entries in the same project pool. The clients apply these updates locally; the server itself does not perform operational transformation or CRDT merging, leaving conflict resolution to the client-side buffer implementation.

### What happens when a user's internet connection drops during a collaboration session?

The server maintains the session state for **30 seconds** after detecting a WebSocket closure. During this window, the `connection_lost` logic (lines 1212-1230) waits for a reconnect attempt. If the client re-establishes the connection within this timeout, the session resumes seamlessly. If not, the server triggers cleanup, removing the user from all project collaborator lists and broadcasting departure notifications to remaining participants.

### How does Zed prevent the collaboration server from being overwhelmed by too many simultaneous users?

Zed implements two layers of backpressure. First, `ConnectionGuard::try_acquire` enforces a global `MAX_CONCURRENT_CONNECTIONS` limit, rejecting new WebSocket attempts with HTTP 503 when capacity is reached. Second, for established connections, the message loop in `handle_connection` (lines 231-250) limits foreground request handlers to `MAX_CONCURRENT_HANDLERS` using a `FuturesUnordered` pool, preventing a single slow operation from blocking message processing for other users.

### What messaging protocol does Zed use for real-time collaboration?

Zed uses a **custom RPC protocol over WebSockets** with binary Protocol Buffers. The message envelope system in [`crates/collab/src/rpc.rs`](https://github.com/zed-industries/zed/blob/main/crates/collab/src/rpc.rs) defines all collaboration actions—such as `ShareProject`, `UpdateBuffer`, and `JoinChannel`—as strongly-typed protobuf messages. This approach reduces parsing overhead compared to JSON and enables type-safe handler registration via Rust's `TypeId` dispatch mechanism.