# AFFiNE Administrator & Developer Guide

## 1. Architecture Overview

AFFiNE is a privacy-focused, local-first knowledge workspace built as a TypeScript + Rust monorepo. It combines a block-based document editor, an infinite canvas whiteboard, and a structured database system into a single application available on web, desktop (Electron), and mobile (Android/iOS) platforms.

### System Architecture

```text
┌─────────────────────────────────────────────────────────────┐
│                      Client Applications                      │
│  ┌──────────┐  ┌──────────────┐  ┌─────────┐  ┌──────────┐ │
│  │ Web App  │  │ Desktop App  │  │ Android │  │   iOS    │ │
│  │ (React)  │  │ (Electron)   │  │(Capacitor│  │ (Swift)  │ │
│  └────┬─────┘  └──────┬───────┘  └────┬────┘  └────┬─────┘ │
│       └────────────────┴───────────────┴────────────┘       │
│                         │                                     │
│              ┌──────────┴──────────┐                         │
│              │     BlockSuite      │                         │
│              │  (Editor Framework) │                         │
│              │  Blocks │ Widgets   │                         │
│              │  GFX    │ Inlines   │                         │
│              └──────────┬──────────┘                         │
│                         │                                     │
│              ┌──────────┴──────────┐                         │
│              │     y-octo (CRDT)   │                         │
│              │   (Rust via NAPI)   │                         │
│              └──────────┬──────────┘                         │
└─────────────────────────┼───────────────────────────────────┘
                          │ GraphQL / WebSocket
┌─────────────────────────┼───────────────────────────────────┐
│              AFFiNE Server (NestJS)                          │
│  ┌──────────────────────┴──────────────────────────────┐    │
│  │                   Server Flavors                     │    │
│  │  graphql │ sync │ renderer │ front │ doc │ allinone  │    │
│  └──────────────────────┬──────────────────────────────┘    │
│              ┌──────────┴──────────┐                         │
│  ┌───────┐  │      Core Modules    │  ┌─────────────────┐  │
│  │ Redis │◄─┤ Auth │ Permission    │  │    Plugins       │  │
│  │(Cache)│  │ Sync │ Storage       │  │ Copilot │ OAuth  │  │
│  └───────┘  │ Quota│ Notification  │  │ Payment │ License│  │
│              └──────────┬──────────┘  │ Indexer │ Worker │  │
│  ┌───────────┐         │              └─────────────────┘  │
│  │PostgreSQL │◄────────┘                                    │
│  │ (Prisma)  │                                              │
│  └───────────┘                                              │
└─────────────────────────────────────────────────────────────┘
```

### Component Inventory

| Component | Path | Role |
|---|---|---|
| **Frontend Core** | `packages/frontend/core/` | Main application shell, modules (cloud, auth, editor, collections, search, AI) |
| **Web App** | `packages/frontend/apps/web/` | Web SPA entry point |
| **Desktop App** | `packages/frontend/apps/electron/` | Electron main process, renderer |
| **Android App** | `packages/frontend/apps/android/` | Capacitor + native Java plugins |
| **iOS App** | `packages/frontend/apps/ios/` | Native Swift plugins |
| **Mobile Shared** | `packages/frontend/apps/mobile-shared/` | Shared mobile code |
| **Admin Panel** | `packages/frontend/admin/` | Server administration UI |
| **Backend Server** | `packages/backend/server/` | NestJS API server |
| **Backend Native** | `packages/backend/native/` | Rust native modules for server |
| **Frontend Native** | `packages/frontend/native/` | Rust NAPI bindings (SQLite, media, schema) |
| **BlockSuite** | `blocksuite/` | Block editor framework (blocks, widgets, GFX, inlines, fragments) |
| **Common Infra** | `packages/common/infra/` | DI framework, ORM, utilities |
| **Common GraphQL** | `packages/common/graphql/` | Generated GraphQL types and queries |
| **y-octo** | `packages/common/y-octo/` | Rust CRDT implementation (Yjs compatible) |
| **Reader** | `packages/common/reader/` | Document parser (delta-to-markdown, DOCX) |
| **S3 Compat** | `packages/common/s3-compat/` | S3-compatible storage adapter |
| **i18n** | `packages/frontend/i18n/` | Internationalization (30+ languages) |
| **CLI Tools** | `tools/cli/` | Build tooling, bundling, dev server |
| **Route System** | `packages/frontend/routes/` | Frontend routing |
| **Analytics/Tracking** | `packages/frontend/track/` | Event tracking |

### Technology Stack

| Layer | Technology |
|---|---|
| Frontend framework | React 18 + Lit (Web Components for BlockSuite) |
| State management | `@preact/signals-core` (reactive signals) |
| Editor framework | BlockSuite (custom, Lit-based) |
| CRDT sync | y-octo (Rust) / Yjs |
| Desktop runtime | Electron 39 |
| Mobile framework | Capacitor 7 (Android), Swift (iOS) |
| Backend framework | NestJS (Node.js) |
| API protocol | GraphQL (via `@nestjs/graphql`) |
| Real-time sync | WebSocket |
| Database | PostgreSQL (via Prisma ORM) |
| Caching/Queue | Redis |
| Native modules | Rust via NAPI-RS |
| Package manager | Yarn 4.13.0 (workspaces) |
| Build tool | Vite 7 |
| Testing | Vitest (unit), Playwright 1.58 (E2E), AVA (backend) |
| Linting | ESLint 9 + oxlint 1.58 + Prettier |
| Language | TypeScript 5.9, Rust (2024 edition) |

> [!NOTE]
> AFFiNE uses a Dependency Injection framework (`packages/common/infra/src/framework/core/`) for frontend service management, modeled after Angular/NestJS patterns. All frontend modules register services through this DI system.

### Data Flow

**Document editing:**
1. User types in BlockSuite editor → block model updates via Yjs document
2. y-octo (Rust CRDT) processes the update locally
3. Update is persisted to local SQLite (desktop) or IndexedDB (web)
4. If cloud sync is enabled, update is sent via WebSocket to the sync server
5. Server merges CRDT updates and broadcasts to other connected clients

**Server API requests:**
1. Client sends GraphQL query/mutation via HTTPS
2. NestJS resolves the request through configured modules
3. Prisma executes database operations against PostgreSQL
4. Response returns to client

---

## 2. System Requirements

### Server Requirements

| Requirement | Minimum | Recommended |
|---|---|---|
| **Node.js** | 18.x | 20.x–22.x (must be &lt;23.0.0) |
| **PostgreSQL** | 14 | 16+ |
| **Redis** | 6 | 7+ |
| **RAM** | 2 GB | 4+ GB |
| **Disk** | 10 GB | 50+ GB (scales with blob storage) |
| **OS** | Linux (x64/arm64) | Ubuntu 22.04+ |

### Development Requirements

| Requirement | Version |
|---|---|
| **Node.js** | &lt;23.0.0 |
| **Yarn** | 4.13.0 (via corepack) |
| **Rust** | Latest stable (2024 edition) |
| **Python** | 3.x (for node-gyp native builds) |
| **Docker** | 20+ (optional, for containerized development) |

> [!IMPORTANT]
> The monorepo uses Yarn 4 Berry with PnP. Do not use npm or pnpm. The `packageManager` field in `package.json` enforces `yarn@4.13.0`.

---

## 3. Configuration Reference

### Server Configuration System

AFFiNE uses a layered configuration system with compile-time defaults, environment variables, JSON config files, and runtime database overrides.

**Configuration precedence (highest to lowest):**

1. Database-stored config (set via GraphQL `updateAppConfig` mutation)
2. JSON config file (`config.json` in project root or `~/.affine/config/config.json`)
3. Environment variables
4. Code defaults (defined via `defineModuleConfig()`)

> [!IMPORTANT]
> Configuration is validated at startup using Zod schemas. Invalid values cause the server to fail fast with descriptive error messages.

### Server Module Config

Defined in `packages/backend/server/src/core/config/config.ts`:

| Key | Env Variable | Type | Default | Description |
|---|---|---|---|---|
| `server.name` | — | `string` | `undefined` | Recognizable name for the server, shown in AFFiNE Desktop |
| `server.externalUrl` | `AFFINE_SERVER_EXTERNAL_URL` | `string` | `""` | Base URL for generating external URLs |
| `server.https` | `AFFINE_SERVER_HTTPS` | `boolean` | `false` | Whether server uses HTTPS |
| `server.host` | `AFFINE_SERVER_HOST` | `string` | `localhost` | FQDN where server is deployed |
| `server.hosts` | — | `string[]` | `[]` | Multiple hosts the server will accept requests from |
| `server.listenAddr` | `LISTEN_ADDR` | `string` | `0.0.0.0` | Address to bind to (e.g., `::` for IPv6) |
| `server.port` | `AFFINE_SERVER_PORT` | `integer` | `3010` | Port to listen on |
| `server.path` | `AFFINE_SERVER_SUB_PATH` | `string` | `""` | URL subpath (e.g., `/affine`) |
| `flags.allowGuestDemoWorkspace` | — | `boolean` | `true` | Allow guest users to create demo workspaces |

### Environment Variables

| Variable | Purpose | Required |
|---|---|---|
| `NODE_ENV` | Environment: `development`, `test`, `production` | No (default: `production`) |
| `AFFINE_ENV` | Namespace: `dev`, `beta`, `production` | No (default: `production`) |
| `DEPLOYMENT_TYPE` | `affine` or `selfhosted` | No (auto-detected) |
| `SERVER_FLAVOR` | Server flavor (see below) | No (default: `allinone`) |
| `DEPLOYMENT_PLATFORM` | `gcp` or `unknown` | No (default: `unknown`) |
| `AFFINE_PRIVATE_KEY` | Auth signing private key | Yes (for auth) |
| `AFFINE_SERVER_EXTERNAL_URL` | Public-facing server URL | Recommended |
| `AFFINE_SERVER_HTTPS` | Enable HTTPS (`true`/`false`) | No |
| `AFFINE_SERVER_HOST` | Server hostname | No (default: `localhost`) |
| `AFFINE_SERVER_PORT` | Server port | No (default: `3010`) |
| `LISTEN_ADDR` | Bind address | No (default: `0.0.0.0`) |
| `AFFINE_SERVER_SUB_PATH` | URL subpath | No |
| `CAPTCHA_SITE_KEY` | CAPTCHA provider site key | No |
| `SENTRY_DSN` | Sentry error tracking DSN | No |
| `CHANGELOG_URL` | URL for changelog feed | No |
| `SELF_HOSTED` | Flag for self-hosted builds | No |
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 storage key | No (cloud only) |
| `GITHUB_TOKEN` | GitHub API token (for changelogs) | No |

### Server Flavors

The server supports multiple deployment flavors for microservice or monolithic architectures:

| Flavor | Value | Description |
|---|---|---|
| **All-in-One** | `allinone` | All services in a single process (default, recommended for self-hosting) |
| **GraphQL** | `graphql` | GraphQL API server only |
| **Sync** | `sync` | WebSocket sync server only |
| **Renderer** | `renderer` | Document rendering service |
| **Front** | `front` | Static frontend + sync + doc services |
| **Doc** | `doc` | Document storage service |
| **Script** | `script` | Script execution mode (explicit only) |

> [!NOTE]
> In `allinone` mode, the server loads ALL modules (GraphQL, sync, renderer, doc, worker). For high-scale deployments, split into separate processes using individual flavors.

### Runtime Configuration via GraphQL

Administrators can update configuration at runtime through the GraphQL API:

```graphql
mutation {
  updateAppConfig(updates: [
    { module: "server", key: "name", value: "My AFFiNE Server" }
  ])
}
```

```graphql
query {
  appConfig
}
```

Configuration validation is available:

```graphql
query {
  validateAppConfig(updates: [
    { module: "server", key: "port", value: 8080 }
  ]) {
    module
    key
    valid
    error
  }
}
```

> [!WARNING]
> Only users with the `Admin` role can access `appConfig` queries and mutations. Invalid config values are rejected with descriptive validation errors.

### JSON Config File

Place a `config.json` in the project root or `~/.affine/config/config.json`:

```json
{
  "$schema": "./node_modules/@affine/server/config-schema.json",
  "server": {
    "name": "My AFFiNE Instance",
    "externalUrl": "https://affine.example.com",
    "https": true,
    "host": "affine.example.com",
    "port": 3010
  }
}
```

### CLI Commands

The server includes a CLI for data migration management:

```bash
# Run the CLI
node --loader ts-node/esm packages/backend/server/src/cli.ts <command>
```

| Command | Description |
|---|---|
| `create [name]` | Create a new data migration script |
| `run` | Run all pending data migrations |
| `revert [name]` | Revert a specific data migration |
| `import-config [path]` | Import configuration from a JSON file |

```bash
# Run all pending migrations
yarn affine run

# Create a new migration
yarn affine create add-user-preferences

# Import config from file
yarn affine import-config ./my-config.json
```

> [!TIP]
> Data migrations are timestamp-ordered and tracked in the `DataMigration` table. Each migration has `up` and `down` methods for forward and rollback operations.

---

## 4. Deployment & Scaling

### Docker Deployment (Self-Hosted)

The recommended self-hosting method uses Docker:

```bash
docker pull ghcr.io/toeverything/affine-graphql:stable
```

**Required services:**

| Service | Purpose |
|---|---|
| **affine-graphql** | Main AFFiNE server (all-in-one) |
| **PostgreSQL** | Primary database |
| **Redis** | Caching, job queue, pub/sub |

**Minimal docker-compose example:**

```yaml
version: '3.8'
services:
  affine:
    image: ghcr.io/toeverything/affine-graphql:stable
    ports:
      - "3010:3010"
    environment:
      NODE_ENV: production
      AFFINE_SERVER_HOST: affine.example.com
      AFFINE_SERVER_HTTPS: "true"
      AFFINE_SERVER_EXTERNAL_URL: https://affine.example.com
      AFFINE_PRIVATE_KEY: "${AFFINE_PRIVATE_KEY}"
      DATABASE_URL: postgresql://affine:password@postgres:5432/affine
      REDIS_SERVER_HOST: redis
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: affine
      POSTGRES_PASSWORD: password
      POSTGRES_DB: affine
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:
```

> [!CAUTION]
> Set a strong, random `AFFINE_PRIVATE_KEY` in production. This key signs authentication tokens — compromising it allows forging user sessions.

### Scaling Strategies

**Horizontal scaling with flavors:**

For high-traffic deployments, split the server into microservices using flavors:

```yaml
# GraphQL API
affine-api:
  image: ghcr.io/toeverything/affine-graphql:stable
  environment:
    SERVER_FLAVOR: graphql

# Sync server (WebSocket)
affine-sync:
  image: ghcr.io/toeverything/affine-graphql:stable
  environment:
    SERVER_FLAVOR: sync

# Document service
affine-doc:
  image: ghcr.io/toeverything/affine-graphql:stable
  environment:
    SERVER_FLAVOR: doc
```

Use a reverse proxy (Nginx, Traefik) to route traffic:
- `/graphql` → `affine-api`
- `/socket.io` → `affine-sync`
- `/*` → `affine-front` (if using front flavor)

### Reverse Proxy Configuration

**Nginx example:**

```nginx
server {
    listen 443 ssl;
    server_name affine.example.com;

    location / {
        proxy_pass http://localhost:3010;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /socket.io {
        proxy_pass http://localhost:3010;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
```

> [!IMPORTANT]
> WebSocket support is required for real-time sync. Ensure your reverse proxy passes the `Upgrade` and `Connection` headers for the `/socket.io` path.

---

## 5. Operations

### Health Monitoring

The server exposes an info endpoint at `GET /info`:

```bash
curl https://affine.example.com/info
```

```json
{
  "compatibility": "0.26.3",
  "message": "AFFiNE 0.26.3 Server",
  "type": "selfhosted",
  "flavor": "allinone"
}
```

> [!TIP]
> Use the `/info` endpoint as a health check target in your load balancer or monitoring system. It responds without authentication.

### Server Features Query

Check enabled features via GraphQL:

```graphql
query {
  serverConfig {
    name
    version
    baseUrl
    type
    features
    initialized
  }
}
```

The `initialized` field returns `true` once at least one user account exists.

### Upgrade Checking

Self-hosted servers automatically check for available upgrades via the `availableUpgrade` resolver:

```graphql
query {
  serverConfig {
    availableUpgrade {
      version
      url
      changelog
      publishedAt
    }
  }
}
```

This queries `https://affine.pro/api/worker/releases` for the latest release in the configured channel (`stable`, `beta`, or `canary` based on `AFFINE_ENV`).

> [!NOTE]
> Upgrade checking only activates for self-hosted deployments (`DEPLOYMENT_TYPE=selfhosted`). Cloud instances return `null`.

### Database Migrations

Run data migrations after upgrades:

```bash
# Inside the container
node --loader ts-node/esm src/cli.ts run
```

Migrations are idempotent — re-running `run` skips already-applied migrations. Some migrations are marked `always: true` and re-execute on every run.

### Backup Strategy

| Component | Backup Method |
|---|---|
| **PostgreSQL** | `pg_dump affine > backup.sql` |
| **Redis** | RDB snapshots or AOF persistence |
| **Blob storage** | Back up the configured storage directory or S3 bucket |
| **Config** | Export via `appConfig` GraphQL query |

### Log Management

The server uses NestJS's built-in logger with structured output. Configure log levels via environment:

```bash
NODE_ENV=production  # Minimal logging
NODE_ENV=development # Verbose logging
```

---

## 6. Performance

### Frontend Performance

- **y-octo (Rust CRDT)**: Native Rust CRDT operations via NAPI-RS provide significantly faster document merge performance compared to pure JavaScript Yjs
- **Turbo Renderer**: The edgeless canvas uses a GPU-accelerated turbo renderer (`blocksuite/affine/gfx/turbo-renderer/`) for large whiteboards
- **Lazy loading**: BlockSuite extensions are loaded on demand via the ext-loader system (`blocksuite/affine/ext-loader/`)
- **SQLite (desktop)**: Desktop app uses native SQLite (via `packages/frontend/native/sqlite_v1/`) for local document storage, providing faster reads than IndexedDB

### Backend Performance

- **Request ID tracing**: Every HTTP/GraphQL request gets a unique ID via `nestjs-cls` for end-to-end tracing
- **Rate limiting**: Built-in throttler (`RateLimiterModule`) prevents abuse. Rate limits can be skipped for specific endpoints using the `@SkipThrottle()` decorator
- **Redis caching**: Frequently accessed data is cached in Redis via `CacheModule`
- **Prisma query optimization**: The `@nestjs-cls/transactional` adapter wraps database operations in transactions with automatic rollback

> [!TIP]
> For large workspaces (1000+ documents), ensure Redis has sufficient memory for sync state caching. Monitor PostgreSQL query performance and add indexes for custom queries.

---

## 7. Security

### Authentication

AFFiNE supports multiple authentication methods:

| Method | Module | Description |
|---|---|---|
| **Email/Password** | `AuthModule` | Built-in credential auth with configurable password requirements |
| **OAuth/OIDC** | `OAuthModule` | External identity providers (Google, GitHub, custom OIDC) |
| **CAPTCHA** | `CaptchaModule` | Bot protection for sign-up and sign-in |

**Password requirements** are configurable via the `auth.passwordRequirements` config:
- `min`: Minimum password length
- `max`: Maximum password length

### Authorization

The permission system uses role-based access control (RBAC) with two resource types:

**Workspace roles:**

| Role | Capabilities |
|---|---|
| **Owner** | Full control, including workspace deletion |
| **Admin** | Manage members, settings, features |
| **Editor** | Create and edit documents |
| **Viewer** | Read-only access |

**Document roles:**

| Role | Capabilities |
|---|---|
| **Owner** | Full document control, sharing, deletion |
| **Editor** | Edit document content |
| **Viewer** | Read-only access |

The `AccessControllerBuilder` (`packages/backend/server/src/core/permission/builder.ts`) provides a fluent API for permission checks:

```typescript
const canEdit = await accessController
  .user(userId)
  .doc(workspaceId, docId)
  .can('Doc.Write');
```

**Workspace policies:**

- Workspaces become read-only when member count or storage exceeds quota
- Read-only reasons: `member_overflow`, `storage_overflow`
- Team workspaces have separate quota tiers

> [!CAUTION]
> Owner role cannot be granted via the API (`Cannot grant Owner role of a workspace to a user`). Ownership transfer requires special procedures.

### API Security

- **Rate limiting**: All API endpoints are rate-limited by default. Use `@SkipThrottle()` only for health/info endpoints
- **Request tracing**: Every request gets a unique ID (`X-Request-Id` header) for audit logging
- **CORS**: Configured per deployment via `server.hosts` (allowed origins)
- **GraphQL introspection**: Should be disabled in production deployments

### Secrets Management

| Secret | Purpose | Storage |
|---|---|---|
| `AFFINE_PRIVATE_KEY` | JWT token signing | Environment variable |
| Database password | PostgreSQL auth | Environment variable / `DATABASE_URL` |
| Redis password | Redis auth | Environment variable |
| OAuth client secrets | OIDC provider auth | Database (encrypted via config system) |
| Copilot API keys | LLM provider auth | Database (via config system) |

> [!WARNING]
> Never commit secrets to version control. Use environment variables or a secrets manager. The `AFFINE_PRIVATE_KEY` must be consistent across all server instances in a scaled deployment.

---

## 8. Developer Internals

### Repository Structure

```text
toeverything/AFFiNE/
├── blocksuite/                  # Block editor framework
│   ├── affine/                  # AFFiNE-specific extensions
│   │   ├── blocks/              # Block types (paragraph, list, code, image, etc.)
│   │   ├── widgets/             # Editor widgets (toolbar, slash-menu, drag-handle)
│   │   ├── gfx/                 # Graphics elements (shape, connector, text, brush)
│   │   ├── inlines/             # Inline elements (link, mention, latex, footnote)
│   │   ├── fragments/           # UI fragments (outline, frame-panel, doc-title)
│   │   ├── shared/              # Shared utilities, services, adapters
│   │   ├── model/               # Block model definitions
│   │   ├── components/          # Reusable UI components
│   │   ├── data-view/           # Database view framework
│   │   ├── rich-text/           # Rich text editing
│   │   ├── foundation/          # Foundation extensions
│   │   ├── ext-loader/          # Extension loading system
│   │   └── all/                 # Barrel package (@blocksuite/affine)
│   ├── framework/               # Core framework
│   │   ├── store/               # Document store, schema, adapters
│   │   ├── std/                 # Standard library (events, inline editing)
│   │   ├── global/              # Global types and utilities
│   │   └── sync/                # Sync utilities
│   ├── playground/              # Development playground
│   └── integration-test/        # Integration tests
├── packages/
│   ├── backend/
│   │   ├── server/              # NestJS server application
│   │   │   └── src/
│   │   │       ├── app.module.ts    # Module composition
│   │   │       ├── cli.ts           # CLI entry point
│   │   │       ├── env.ts           # Environment configuration
│   │   │       ├── base/            # Base infrastructure
│   │   │       │   ├── config/      # Configuration system
│   │   │       │   ├── error/       # Error handling (233 error types)
│   │   │       │   ├── event/       # Event bus
│   │   │       │   └── ...          # Cache, Redis, storage, etc.
│   │   │       ├── core/            # Core business logic
│   │   │       │   ├── auth/        # Authentication
│   │   │       │   ├── permission/  # RBAC authorization
│   │   │       │   ├── config/      # Server config resolver
│   │   │       │   ├── sync/        # Real-time sync
│   │   │       │   ├── doc/         # Document storage
│   │   │       │   ├── quota/       # Usage quotas
│   │   │       │   └── ...
│   │   │       ├── plugins/         # Optional plugins
│   │   │       │   ├── copilot/     # AI copilot (multi-provider)
│   │   │       │   ├── oauth/       # OAuth/OIDC
│   │   │       │   ├── payment/     # Stripe payments
│   │   │       │   ├── license/     # License management
│   │   │       │   ├── indexer/     # Full-text search indexer
│   │   │       │   └── ...
│   │   │       ├── models/          # Data models (Prisma)
│   │   │       └── data/            # Migrations
│   │   └── native/              # Rust native modules (server-side)
│   ├── frontend/
│   │   ├── core/                # Main application code
│   │   │   └── src/modules/     # Feature modules (50+ modules)
│   │   ├── apps/
│   │   │   ├── web/             # Web SPA entry
│   │   │   ├── electron/        # Electron app
│   │   │   ├── android/         # Android (Capacitor)
│   │   │   ├── ios/             # iOS (Swift)
│   │   │   └── mobile/          # Mobile web
│   │   ├── admin/               # Admin panel
│   │   ├── native/              # Rust NAPI modules
│   │   │   ├── nbstore/         # Native blob/doc store
│   │   │   ├── sqlite_v1/       # SQLite bindings
│   │   │   ├── schema/          # Schema validation
│   │   │   └── media_capture/   # Audio/video capture
│   │   ├── component/           # Shared UI components
│   │   ├── i18n/                # Internationalization
│   │   ├── routes/              # Frontend routing
│   │   └── track/               # Analytics
│   └── common/
│       ├── infra/               # DI framework, ORM, utilities
│       ├── graphql/             # Generated GraphQL client
│       ├── native/              # Shared Rust modules
│       ├── y-octo/              # CRDT implementation
│       ├── reader/              # Document parsers
│       ├── s3-compat/           # S3 storage adapter
│       ├── error/               # Error types
│       ├── debug/               # Debug utilities
│       └── nbstore/             # Notebook store interface
├── tools/
│   ├── cli/                     # Build and dev CLI
│   ├── utils/                   # Build config utilities
│   ├── doc-diff/                # Documentation diff tool
│   ├── commitlint/              # Commit message linting
│   └── playstore-auto-bump/     # Play Store version bumper
├── tests/
│   ├── affine-local/            # Local-mode E2E tests
│   ├── affine-cloud/            # Cloud E2E tests
│   ├── affine-desktop/          # Desktop E2E tests
│   ├── affine-desktop-cloud/    # Desktop + cloud E2E tests
│   ├── affine-mobile/           # Mobile E2E tests
│   ├── affine-cloud-copilot/    # AI copilot E2E tests
│   ├── blocksuite/              # BlockSuite E2E tests
│   └── kit/                     # Test utilities and helpers
├── docs/
│   ├── contributing/            # Contributing guides
│   └── reference/               # API reference docs
├── scripts/                     # Setup scripts
├── package.json                 # Root workspace config
├── Cargo.toml                   # Rust workspace config
└── .oxlintrc.json               # Linting configuration
```

### BlockSuite Architecture

BlockSuite is AFFiNE's custom block-based editor framework, built on Lit Web Components. It provides:

**Core concepts:**

| Concept | Description |
|---|---|
| **Block** | Fundamental content unit (paragraph, list, image, etc.) |
| **Store** | Document state container backed by Yjs |
| **Schema** | Type definitions for block models |
| **Command** | Composable editor operations (chainable via `.pipe()`) |
| **Extension** | Plugin system (StoreExtensionProvider, ViewExtensionProvider) |
| **Widget** | Floating UI elements (toolbar, slash menu, drag handle) |
| **GFX** | Graphics framework for edgeless mode (shapes, connectors) |
| **Inline** | Inline formatting and elements (bold, link, mention) |

**Extension pattern:**

Every block type follows a consistent pattern:

```typescript
// Store extension — registers the block model
export class MyBlockStoreExtension extends StoreExtensionProvider { ... }

// View extension — registers the block component
export class MyBlockViewExtension extends ViewExtensionProvider { ... }
```

**Block flavors registered in the system:**

- `affine:paragraph`, `affine:list`, `affine:code`, `affine:image`
- `affine:bookmark`, `affine:attachment`, `affine:callout`
- `affine:divider`, `affine:latex`, `affine:embed`
- `affine:database`, `affine:data-view`, `affine:table`
- `affine:surface`, `affine:surface-ref`, `affine:note`
- `affine:frame`, `affine:edgeless-text`

### Frontend Module System

The frontend uses a DI-based module system in `packages/frontend/core/src/modules/`. Each module follows this pattern:

```text
modules/<feature>/
├── services/          # Business logic services (extend Service class)
├── views/             # React components
├── utils/             # Helper functions
└── index.ts           # Module registration
```

**Key frontend modules (50+):**

| Module | Purpose |
|---|---|
| `cloud/` | Cloud sync, auth, workspace management (27 service files) |
| `editor-setting/` | Editor preferences |
| `permissions/` | Permission checks and member management |
| `collection/` | Collection management |
| `collection-rules/` | Smart collection filter rules |
| `docs-search/` | Full-text document search |
| `doc/` | Document lifecycle |
| `doc-summary/` | AI-generated document summaries |
| `workbench/` | Multi-tab workspace UI |
| `workspace/` | Workspace management |
| `ai-button/` | AI copilot button and UI |
| `media/` | Audio/video recording and transcription |
| `dnd/` | Drag-and-drop system |
| `find-in-page/` | Ctrl+F find functionality |
| `journal/` | Daily journal entries |
| `notification/` | User notifications |
| `quicksearch/` | Quick search (Ctrl+K) |
| `favorite/` | Favorite pages |
| `import-template/` | Template importing |
| `import-clipper/` | Web clipper import |
| `peek-view/` | Peek preview of linked docs |
| `navigation/` | Page navigation |
| `theme-editor/` | Theme customization |
| `pdf/` | PDF rendering |
| `at-menu-config/` | @-mention menu configuration |

### Backend Module Architecture

The NestJS server (`packages/backend/server/`) uses a modular architecture with conditional loading:

```typescript
// From app.module.ts — modules loaded based on server flavor
export function buildAppModule(env: Env) {
  const factor = new AppModuleBuilder();
  factor
    .use(...FunctionalityModules)  // Always loaded
    .use(UserModule, AuthModule, PermissionModule)  // Auth
    .useIf(() => env.flavors.graphql, GqlModule, ...)  // GraphQL only
    .useIf(() => env.flavors.sync, SyncModule, ...)  // Sync only
    .useIf(() => env.dev || env.selfhosted, WorkerModule, SelfhostModule)
  return factor.compile();
}
```

**Always-loaded modules (FunctionalityModules):**

| Module | Purpose |
|---|---|
| `ClsModule` | Request-scoped context (unique request IDs) |
| `LoggerModule` | Structured logging |
| `PrismaModule` | Database access |
| `EventModule` | Async event bus |
| `ConfigModule` | Configuration management |
| `RedisModule` | Redis client |
| `CacheModule` | Caching layer |
| `MutexModule` | Distributed locks |
| `MetricsModule` | Performance metrics |
| `RateLimiterModule` | API rate limiting |
| `StorageProviderModule` | Storage abstraction |
| `WebSocketModule` | WebSocket support |
| `JobModule` | Background job processing |
| `ModelsModule` | Data model layer |
| `MonitorModule` | Health monitoring |

### Copilot (AI) Provider System

The copilot plugin (`packages/backend/server/src/plugins/copilot/providers/`) supports multiple LLM backends:

| Provider Type | Implementation | Capabilities |
|---|---|---|
| **OpenAI** | OpenAI API | Text, image, embeddings |
| **Google Gemini** | Google AI API | Text, image, audio, file |
| **Cloudflare Workers AI** | Workers AI API | Text, reranking |
| **Native (llm_adapter)** | Rust native adapter | Text streaming, reranking |

Providers are registered via `CopilotProviderFactory` which resolves models based on capability requirements (input types, output types). Configuration is stored in `config.copilot.providers`.

### ORM Layer

The frontend uses a custom ORM (`packages/common/infra/src/orm/core/`) for client-side data:

```typescript
// Define a table schema
const UserSettingsTable = new Table('user_settings', {
  id: f.string().primaryKey(),
  theme: f.string().default('system'),
  language: f.string().default('en'),
});
```

Adapters: The ORM supports pluggable storage adapters (`packages/common/infra/src/orm/core/adapters/`).

### Error Handling

The backend defines a comprehensive error hierarchy in `packages/backend/server/src/base/error/`:

- `UserFriendlyError` — base class for errors shown to users
- `GraphQLError` — GraphQL-specific errors
- Domain-specific errors: `DocActionDenied`, `SpaceAccessDenied`, `OwnerCanNotLeaveWorkspace`, `InvalidAppConfigInput`, etc.

The system includes 233 error types covering authentication, authorization, quota, sync, and configuration failures.

### Testing Strategy

| Test Type | Framework | Location | Command |
|---|---|---|---|
| **Unit tests** | Vitest | `packages/*/src/__tests__/` | `yarn test` |
| **Backend tests** | AVA | `packages/backend/server/src/__tests__/` | `yarn test` |
| **E2E (web)** | Playwright | `tests/affine-local/`, `tests/affine-cloud/` | `yarn playwright test` |
| **E2E (desktop)** | Playwright | `tests/affine-desktop/` | Dedicated config |
| **E2E (mobile)** | Playwright | `tests/affine-mobile/` | Dedicated config |
| **E2E (copilot)** | Playwright | `tests/affine-cloud-copilot/` | Dedicated config |
| **BlockSuite E2E** | Playwright | `tests/blocksuite/` | Dedicated config |
| **Integration** | Vitest | `blocksuite/integration-test/` | `yarn test` |

**Test utilities:**

- `tests/kit/` — shared test helpers, Playwright fixtures, mock utilities
- `packages/backend/server/src/__tests__/utils/` — backend test app factory, mock user creation
- `Mockers` class — factory for creating test users, workspaces, workspace members

> [!NOTE]
> E2E tests use Playwright 1.58.2. The test infrastructure supports optional Istanbul coverage collection via `COVERAGE=true` and CPU throttling via `CPU_THROTTLE` environment variable.

### Code Conventions

| Convention | Rule |
|---|---|
| **File naming** | `kebab-case` (enforced by oxlint `unicorn/filename-case`) |
| **Import order** | Sorted by `simple-import-sort` |
| **Type imports** | `import type { ... }` enforced by `typescript/consistent-type-imports` |
| **No barrel re-exports from dist/src** | `no-restricted-imports` rule prevents deep imports |
| **Commit messages** | Commitlint enforced (`tools/commitlint/`) |
| **Formatting** | Prettier (JS/TS), `taplo` (TOML), `cargo fmt` (Rust) |
| **Module style** | NestJS decorators for backend, DI Service classes for frontend |

### Build System

```bash
# Development
yarn dev                    # Start dev server
yarn build                  # Production build
yarn typecheck              # TypeScript type checking
yarn lint                   # Run all linters (oxlint + eslint + prettier)
yarn lint:fix               # Auto-fix lint issues

# Testing
yarn test                   # Run unit tests
yarn test:ui                # Run tests with Vitest UI
yarn test:coverage          # Run tests with coverage

# Post-install
yarn affine init            # Initialize workspace (runs postinstall)
```

Build config is managed by `tools/cli/` which provides the `yarn affine` command (aliased as `yarn af`). This handles development server startup, production bundling, and initialization.

> [!TIP]
> Use `DISABLE_DEV_OVERLAY=true` to suppress the development overlay. Set `SELF_HOSTED=true` when building for self-hosted distribution.

### Native Modules (Rust)

The project includes Rust native modules compiled via NAPI-RS:

| Module | Path | Purpose |
|---|---|---|
| **Frontend native** | `packages/frontend/native/` | SQLite, schema validation, media capture |
| **Backend native** | `packages/backend/native/` | Server-side native operations |
| **y-octo** | `packages/common/y-octo/` | CRDT implementation |
| **Common native** | `packages/common/native/` | Hashcash, shared utilities |
| **Mobile native** | `packages/frontend/mobile-native/` | Mobile platform bridges |

Native modules are built using the Rust 2024 edition with release profile optimizations (LTO, single codegen unit, symbol stripping).

### S3-Compatible Storage

The `packages/common/s3-compat/` module provides an S3-compatible storage adapter supporting:

- AWS S3
- Cloudflare R2
- MinIO
- Any S3-compatible object storage

This is used for blob storage (images, attachments, files) in cloud and self-hosted deployments.

---

## 9. Integration Points

### GraphQL API

The server exposes a GraphQL API (when running `graphql` or `allinone` flavor) for:

- Workspace management (CRUD, members, permissions)
- Document operations
- User authentication and account management
- Configuration management (admin only)
- Copilot AI interactions
- Payment and subscription management
- Server health and version information

Public endpoints (no auth required):
- `serverConfig` query — server version, features, name

### WebSocket Sync

Real-time document synchronization uses WebSocket connections (Socket.IO). The sync protocol is based on Yjs/y-octo CRDT updates:

1. Client connects via WebSocket
2. Initial sync: full document state exchange
3. Incremental sync: delta updates broadcast to all connected clients
4. Conflict resolution: automatic CRDT merge (no manual conflict resolution needed)

### MCP (Model Context Protocol)

AFFiNE supports MCP for AI tool integration, allowing external AI agents to create and manipulate workspace documents via WebSocket API.

### OAuth/OIDC Integration

Configure external identity providers via the `OAuthModule`:

- Google OAuth
- GitHub OAuth
- Custom OIDC providers

OIDC configuration requires the provider to return `email` in token claims.

### Email (SMTP)

The `MailModule` sends transactional emails (invitations, notifications) via configured SMTP. The `MAILER_NAME` environment variable controls the HELO/EHLO name.

> [!WARNING]
> If `MAILER_NAME` is not set, nodemailer sends an empty HELO command, which strict SMTP servers reject. (Community issue [#14842](https://github.com/toeverything/AFFiNE/issues/14842))

### Payment Integration

The `PaymentModule` integrates with Stripe for subscription and per-feature billing. Requires `stripe` npm package and API keys configured in the server.

### Calendar Integration

The `CalendarModule` (`packages/backend/server/src/plugins/calendar/providers/`) provides calendar event integration with external providers.

---

_Generated May 2026 from toeverything/AFFiNE at df482c9. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
