# How AppFlowy Cloud Sync and Server Integration Works: Architecture and Implementation

> Discover how AppFlowy Cloud sync and server integration works with its provider-based abstraction layer, enabling seamless two-way sync between local SQLite and the cloud.

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

---

**AppFlowy Cloud sync and server integration uses a provider-based abstraction layer that lazily instantiates either a local mock server or a remote AppFlowy Cloud backend based on the authentication type, enabling seamless two-way synchronization between local SQLite storage and the cloud.**

AppFlowy stores all user data locally in an SQLite database while maintaining optional synchronization with **AppFlowy Cloud** through a sophisticated Rust-based server layer. This article examines the exact mechanisms that handle authentication, server selection, and workspace synchronization as implemented in the AppFlowy-IO/AppFlowy repository.

## Core Architecture: The Server Provider Pattern

The foundation of AppFlowy Cloud sync relies on a type-safe server provider that abstracts away network implementation details from the UI layer.

### Server Selection Based on AuthType

The system distinguishes between offline and cloud modes using the **`AuthType`** enum defined in `flowy_user_pub::entities.rs`. The **`ServerProvider`** struct in [`frontend/rust-lib/flowy-core/src/server_layer.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-core/src/server_layer.rs) (lines 27-73) maintains a map of authentication types to server implementations, lazily creating the appropriate backend when first requested:

```rust
pub fn get_server(&self) -> FlowyResult<Arc<dyn AppFlowyServer>> {
    let auth_type = self.get_auth_type();
    if let Some(r) = self.providers.get(&auth_type) {
        return Ok(r.value().clone());
    }
    
    let server: Arc<dyn AppFlowyServer> = match auth_type {
        AuthType::Local => Arc::new(LocalServer::new(...)),
        AuthType::AppFlowyCloud => {
            let cfg = self.config.cloud_config.clone()
                .ok_or_else(|| FlowyError::internal().with_context("Missing cloud config"))?;
            Arc::new(AppFlowyCloudServer::new(cfg, ...))
        },
    };
    self.providers.insert(auth_type, server);
    Ok(self.providers.get(&auth_type).unwrap().clone())
}

```

When `AuthType::AppFlowyCloud` is active, the provider returns an **`AppFlowyCloudServer`** instance (defined in [`frontend/rust-lib/flowy-server/src/af_cloud/server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/af_cloud/server.rs), lines 33-50) that communicates via HTTP API. For offline usage or tests, it instantiates **`LocalServer`** from [`frontend/rust-lib/flowy-server/src/local_server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/local_server.rs).

### Unified Cloud Service Interface

All cloud operations flow through the **`AppFlowyServer`** trait (lines 62-71 of [`frontend/rust-lib/flowy-server/src/server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/server.rs)), which exposes specialized services for users, folders, documents, databases, AI, and storage. The user-specific subset is defined in the **`UserCloudService`** trait located in [`frontend/rust-lib/flowy-user-pub/src/cloud.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user-pub/src/cloud.rs) (lines 124-150), providing methods for sign-in, workspace creation, and member management.

The **`UserCloudServiceProvider`** trait (same file, lines 62-74) abstracts token handling and server retrieval. `ServerProvider` implements this in [`frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs) (lines 65-78), delegating to the underlying `AppFlowyServer`:

```rust
impl UserCloudServiceProvider for ServerProvider {
    fn set_server_auth_type(&self, auth_type: &AuthType, token: Option<String>) -> FlowyResult<()> {
        self.set_auth_type(*auth_type);
        if let Some(tok) = token { self.set_token(&tok)?; }
        Ok(())
    }
    
    fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
        Ok(self.get_server()?.user_service())
    }
}

```

## Workspace Synchronization Flow

When a user opens a workspace, the system orchestrates a coordinated sync between local SQLite storage and the remote cloud state.

### Opening a Workspace with Cloud Sync

The **`UserManager::open_workspace`** method in [`frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs) (lines 141-199) handles the complete initialization sequence:

1. **Authentication setup** – Configures the server provider with the current `AuthType` and retrieves the access token.
2. **Local database check** – Queries SQLite for existing workspace data using `select_user_workspace`.
3. **Cloud reconciliation** – If the workspace is missing locally, calls **`sync_workspace`** to fetch from the cloud.
4. **Background refresh** – If local data exists, spawns a `tokio::spawn` task to re-sync with the server asynchronously.

```rust
pub async fn open_workspace(&self, workspace_id: &Uuid, workspace_type: WorkspaceType) -> FlowyResult<()> {
    let auth_type = AuthType::from(workspace_type);
    let token = self.token_from_auth_type(&auth_type)?;
    let cloud_service = self.cloud_service()?;
    cloud_service.set_server_auth_type(&auth_type, token)?;

    let profile = self.get_user_profile_from_disk(uid, &workspace_id_str).await?;
    cloud_service.set_token(&profile.token)?;

    let user_workspace = match select_user_workspace(&workspace_id_str, &mut conn) {
        Err(err) if err.is_record_not_found() => {
            sync_workspace(workspace_id, cloud_service.get_user_service()?, uid, workspace_type, self.db_pool(uid)?).await?
        },
        Ok(row) => {
            let user_workspace = UserWorkspace::from(row);
            let pool = self.db_pool(uid)?;
            tokio::spawn(async move {
                let _ = sync_workspace(workspace_id, user_service, uid, workspace_type, pool).await;
            });
            user_workspace
        }
    };
    // UI notification and awareness initialization follow...
}

```

### The Sync Implementation

The **`sync_workspace`** function (lines 809-821 of the same file) pulls the latest workspace state from the cloud and persists it locally:

```rust
async fn sync_workspace(
    workspace_id: &Uuid,
    user_service: Arc<dyn UserCloudService>,
    uid: i64,
    workspace_type: WorkspaceType,
    pool: Arc<ConnectionPool>,
) -> FlowyResult<UserWorkspace> {
    let user_workspace = user_service.open_workspace(workspace_id).await?;
    if let Ok(mut conn) = pool.get() {
        upsert_user_workspace(uid, workspace_type, user_workspace.clone(), &mut conn)?;
    }
    Ok(user_workspace)
}

```

For settings updates, **`sync_workspace_settings`** (lines 453-473) pushes local changes to the cloud via `user_service.set_workspace_settings` before updating the local SQLite cache.

## Implementation Example: Initializing Cloud Sync

The following example demonstrates how to initialize the server provider and open a cloud-enabled workspace:

```rust
use flowy_core::AppFlowyCoreConfig;
use flowy_core::server_layer::ServerProvider;
use flowy_user::user_manager::UserManager;
use uuid::Uuid;
use flowy_user_pub::entities::WorkspaceType;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = AppFlowyCoreConfig::load().await?;
    
    let server_provider = ServerProvider::new(
        config,
        Default::default(),
        flowy_server::guest_dto::LoggedUserDummy,
        None,
    );

    let user_manager = UserManager::new(server_provider.clone());
    
    let workspace_id = Uuid::parse_str("9f1e2c6a-b3f8-4d5a-a9d2-c8e2d8f9b4a1")?;
    user_manager
        .open_workspace(&workspace_id, WorkspaceType::Personal)
        .await?;

    println!("Workspace opened & synced!");
    Ok(())
}

```

This initialization creates the lazy server map, but the actual `AppFlowyCloudServer` instantiation occurs only when `open_workspace` triggers `get_server()` with `AuthType::AppFlowyCloud`.

## Summary

- **Lazy server initialization**: The `ServerProvider` delays cloud connection establishment until explicitly requested, supporting both `LocalServer` (offline) and `AppFlowyCloudServer` (remote) modes through the unified `AppFlowyServer` trait.
- **Type-safe authentication**: The `AuthType` enum drives server selection, ensuring that token handling and service instantiation remain consistent across the application.
- **Two-tier synchronization**: Workspace opening performs an immediate blocking sync if data is missing locally, followed by a non-blocking background refresh via `tokio::spawn` to maintain eventual consistency without freezing the UI.
- **Unified service traits**: All cloud operations flow through `UserCloudServiceProvider` and `UserCloudService` abstractions, decoupling the UI from HTTP implementation details defined in `flowy-server/src/af_cloud/`.

## Frequently Asked Questions

### How does AppFlowy handle offline mode versus cloud sync?

AppFlowy uses the `AuthType` enum to distinguish between `Local` and `AppFlowyCloud` modes. When `AuthType::Local` is set, the `ServerProvider` returns a `LocalServer` stub that satisfies the `AppFlowyServer` trait without making network calls, allowing full functionality with only SQLite storage. Switching to `AuthType::AppFlowyCloud` lazily instantiates the real cloud client and initiates synchronization.

### What happens when a workspace is opened for the first time on a new device?

The `UserManager::open_workspace` method checks the local SQLite database via `select_user_workspace`. If no record exists (indicated by `is_record_not_found()`), it blocks the UI thread to call `sync_workspace`, which fetches the complete workspace state from `user_service.open_workspace` and persists it using `upsert_user_workspace`. Subsequent opens trigger a background refresh instead.

### Where is the cloud synchronization logic located in the codebase?

The primary synchronization logic resides in three key locations: [`frontend/rust-lib/flowy-core/src/server_layer.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-core/src/server_layer.rs) handles server selection and caching; [`frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs) contains the `open_workspace` orchestration and `sync_workspace` implementation; and [`frontend/rust-lib/flowy-server/src/af_cloud/server.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-server/src/af_cloud/server.rs) provides the concrete HTTP client for AppFlowy Cloud operations.

### How does AppFlowy ensure data consistency between local and remote storage?

The system implements **eventual consistency** through a combination of immediate blocking fetches for missing data and fire-and-forget background updates. When local changes occur, dedicated sync methods like `sync_workspace_settings` push updates immediately. For incoming changes, a background `tokio::spawn` task refreshes the local SQLite cache without blocking user interaction, ensuring the UI remains responsive while maintaining synchronization.