# How AppFlowy Synchronizes Real-Time Collaborative Features Using CRDTs and WebSockets

> Discover how AppFlowy synchronizes real-time collaborative features using CRDTs and WebSockets. Learn about its CRDT-based sync stack and live collaboration.

- Repository: [AppFlowy-IO/AppFlowy](https://github.com/AppFlowy-IO/AppFlowy)
- Tags: internals
- Published: 2026-03-03

---

**AppFlowy synchronizes real-time collaborative features through a CRDT-based sync stack that connects local Collab objects to AppFlowy Cloud via WebSockets, using a configurable builder pattern to conditionally attach SyncPlugins when live collaboration is enabled.**

The open-source AppFlowy-IO/AppFlowy repository implements real-time collaboration by layering a plugin-based synchronization system on top of conflict-free replicated data types (CRDTs). This architecture allows document managers to enable or disable live syncing per object while abstracting network concerns away from the UI layer.

## The CRDT-Based Sync Architecture

AppFlowy’s real-time collaboration relies on a **Collab** object model backed by the `y-crdt` library. The sync infrastructure is divided into four distinct layers that handle configuration, plugin injection, transport, and persistence.

### Collab Builder and Configuration

Every collaborative object (documents, folders, user awareness) originates from the **CollabBuilder** in [`frontend/rust-lib/collab-integrate/src/collab_builder.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/collab-integrate/src/collab_builder.rs). The builder consults `CollabBuilderConfig::sync_enable` (default `false`) to decide whether the object should participate in real-time collaboration.

```rust
// Lines 401-412 in collab_builder.rs
let config = CollabBuilderConfig::default().sync_enable(true);
let collab = collab_builder
    .build_collab(workspace_id, uid, object_id, CollabType::Document, config)
    .await?;

```

When `sync_enable` is set to `true` (typically by document managers such as [`flowy-document/src/manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/flowy-document/src/manager.rs) at line 214), the builder prepares the object for network synchronization.

### SyncPlugin and WebSocket Transport

If synchronization is enabled, the builder attaches a **SyncPlugin** to the Collab instance. This plugin opens a `WebSocketChannel<ServerCollabMessage>` via the `client_api::collab_sync` crate, streaming local CRDT operations to the server and applying inbound updates.

```rust
// Line 331 in collab_builder.rs
if build_config.sync_enable {
    let sync_plugin = SyncPlugin::new(...);
    collab_builder.with_plugin(Box::new(sync_plugin));
}

```

The `SyncPlugin` is imported in [`frontend/rust-lib/flowy-core/src/deps_resolve/cloud_service_impl.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-core/src/deps_resolve/cloud_service_impl.rs) (line 2) and encapsulates all networking logic, ensuring the UI remains agnostic of connection state.

### Cloud Service Provider Abstraction

The **CollabCloudPluginProvider** trait in [`frontend/rust-lib/collab-integrate/src/plugin_provider.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/collab-integrate/src/plugin_provider.rs) (lines 10-26) determines which plugins load based on the execution environment. It implements `is_sync_enabled` to return the configuration flag, allowing the same codebase to operate in offline (no plugin) or cloud modes.

```rust
// From plugin_provider.rs and cloud_service_impl.rs
impl CollabCloudPluginProvider for MyProvider {
    fn is_sync_enabled(&self) -> bool { self.sync_enabled }
    fn get_plugins(&self, ctx: CollabPluginProviderContext) -> Vec<Box<dyn CollabPlugin>> {
        if ctx.is_cloud() && self.is_sync_enabled() {
            vec![Box::new(SyncPlugin::new(...))]
        } else {
            vec![] // NoSyncPlugin implicit via empty vec
        }
    }
}

```

## How the Synchronization Flow Works

The end-to-end real-time collaboration flow follows six distinct steps:

1. **Object Creation**: A manager (e.g., document, folder, or user-awareness manager) creates a `Collab` instance with `sync_enable(true)` via the builder.
2. **Plugin Injection**: The builder detects the sync flag and attaches the `SyncPlugin`, which initializes a WebSocket connection to AppFlowy Cloud.
3. **Local Edit Transformation**: User edits are transformed by `y-crdt` into compact operations (ops).
4. **Upstream Transmission**: The `SyncPlugin` sends these ops as `ServerCollabMessage` structs over the WebSocket.
5. **Server Processing**: The cloud service receives messages, persists them to the shared **CollabKVDB** (a KV CRDT store), and broadcasts to subscribed clients.
6. **Downstream Application**: Remote clients receive ops, apply them to local CRDT instances, and update the UI instantly.

If `sync_enable` is false or the network is unavailable, the `Collab` operates purely locally with no plugin attached.

## Key Source Files and Implementation Details

| File Path | Responsibility |
|-----------|----------------|
| [`frontend/rust-lib/collab-integrate/src/collab_builder.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/collab-integrate/src/collab_builder.rs) | Builds `Collab` instances and conditionally injects `SyncPlugin` at line 331 based on `sync_enable` configuration (lines 401-412). |
| [`frontend/rust-lib/collab-integrate/src/plugin_provider.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/collab-integrate/src/plugin_provider.rs) | Defines the `CollabCloudPluginProvider` trait (lines 10-26) that selects cloud sync or local-only plugins. |
| [`frontend/rust-lib/flowy-core/src/deps_resolve/cloud_service_impl.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-core/src/deps_resolve/cloud_service_impl.rs) | Concrete implementation supplying the `SyncPlugin` via `client_api::collab_sync` imports (line 2). |
| [`frontend/rust-lib/flowy-server/src/server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/server.rs) | WebSocket server creating `Arc<WebSocketChannel<ServerCollabMessage>>` for each client at line 142. |
| [`frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs) | Manages user-awareness CRDT with sync enabled; references `CollabKVDB` at line 335. |
| [`frontend/rust-lib/flowy-document/src/manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-document/src/manager.rs) | Enables sync for documents at line 214 via `CollabBuilderConfig::sync_enable`. |
| [`frontend/rust-lib/flowy-folder/src/manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-folder/src/manager.rs) | Enables sync for folder hierarchies. |
| [`frontend/rust-lib/flowy-database2/src/manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-database2/src/manager.rs) | Enables sync for database objects. |

## Code Examples

### Enabling Sync for Documents and Objects

Document managers explicitly opt into real-time collaboration by setting the sync flag before building the Collab object:

```rust
// flowy-document/src/manager.rs (line 214)
let config = CollabBuilderConfig::default().sync_enable(sync_enable);
let collab = self
    .collab_builder
    .await?
    .build_collab(
        workspace_id,
        uid,
        object_id,
        CollabType::Document,
        config,
    )
    .await?;

```

### Attaching the SyncPlugin in the Builder

The builder logic branches at line 331 to attach the networking plugin only when configured:

```rust
// collab-integrate/src/collab_builder.rs
if build_config.sync_enable {
    // SyncPlugin uses client_api::collab_sync for WebSocket transport
    let sync_plugin = SyncPlugin::new(...);
    collab_builder.with_plugin(Box::new(sync_plugin));
}

```

### Providing Cloud Plugins via the Provider Trait

The provider abstraction enables runtime decisions about whether to load sync capabilities:

```rust
// cloud_service_impl.rs (excerpt) and plugin_provider.rs
impl CollabCloudPluginProvider for MyProvider {
    fn is_sync_enabled(&self) -> bool { self.sync_enabled }
    fn get_plugins(&self, ctx: CollabPluginProviderContext) -> Vec<Box<dyn CollabPlugin>> {
        if ctx.is_cloud() && self.is_sync_enabled() {
            vec![Box::new(SyncPlugin::new(...))]
        } else {
            vec![]
        }
    }
}

```

### Server-Side WebSocket Handling

The server maintains per-client channels to broadcast CRDT operations:

```rust
// flowy-server/src/server.rs (line 142)
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let channel = Arc::new(WebSocketChannel::<ServerCollabMessage>::new(tx, rx));
self.clients.insert(uid, channel.clone());

```

## Summary

- AppFlowy uses a **CRDT-based sync stack** (`y-crdt`) to guarantee conflict-free real-time collaboration across clients.
- The **CollabBuilder** in [`collab_builder.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/collab_builder.rs) controls synchronization via the `sync_enable` configuration flag, defaulting to offline mode.
- **SyncPlugin** attaches only when enabled, managing WebSocket connections through `client_api::collab_sync` and abstracting network logic from the UI.
- The **CollabCloudPluginProvider** trait enables runtime environment detection, supporting both cloud-synced and local-only execution modes.
- Server-side components in [`flowy-server/src/server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/flowy-server/src/server.rs) handle `ServerCollabMessage` streams, broadcasting operations to all subscribed clients via WebSocket channels.

## Frequently Asked Questions

### What CRDT library does AppFlowy use for real-time collaboration?

AppFlowy builds its collaborative documents on **y-crdt**, a Rust implementation of the Yjs CRDT algorithm. The library handles operation transformation, conflict resolution, and state merging automatically, ensuring that concurrent edits from multiple users converge to the same document state without requiring a central authority to lock files.

### How does AppFlowy handle offline editing?

When `CollabBuilderConfig::sync_enable` is set to `false` or the network is unavailable, the builder attaches **NoSyncPlugin** (or no plugin at all), allowing the `Collab` object to function purely locally using `CollabKVDB` for persistence. Once connectivity returns, managers can rebuild the object with `sync_enable(true)` to re-establish the WebSocket channel and sync pending changes.

### Can individual documents disable real-time synchronization?

Yes. Real-time synchronization is opt-in per object. Managers for documents (`flowy-document`), folders (`flowy-folder`), and databases (`flowy-database2`) each control the `sync_enable` flag independently when calling `build_collab()`. This allows some workspace items to remain local-only while others collaborate live.

### What message format travels over the WebSocket?

The WebSocket transports **ServerCollabMessage** structs defined in the `client_api::collab_sync` crate. These messages encapsulate compact binary operations (ops) generated by the CRDT layer, carrying update deltas, awareness state (cursors, selections), and sync acknowledgments between the client `SyncPlugin` and the cloud server.