# AFFiNE Block-Based Editor: Core Features and Architecture Explained

> Discover AFFiNE's core block editor features including dual editing modes, real-time Yjs sync, and extensible architecture. Explore the modular document system today.

- Repository: [Toeverything/AFFiNE](https://github.com/toeverything/AFFiNE)
- Tags: deep-dive
- Published: 2026-03-05

---

**AFFiNE's block-based editor provides a modular, collaborative document system with dual editing modes (Page and Edgeless), real-time Yjs synchronization, and an extensible block schema architecture.**

The AFFiNE block-based editor is the core document engine powering the AFFiNE knowledge management platform. Built on the Blocksuite framework, it treats every piece of content—from paragraphs to tables to canvas shapes—as discrete, composable blocks. This architecture enables seamless switching between linear document editing and free-form whiteboarding while maintaining real-time collaboration through Yjs-based conflict resolution.

## Dual Editing Modes: Page and Edgeless

The AFFiNE block-based editor exposes two primary editing surfaces through distinct web components, both extending `SignalWatcher(WithDisposable(ShadowlessElement))` to enable reactive updates and lifecycle management.

### PageEditor for Linear Documents

The `PageEditor` component in [`packages/frontend/core/src/blocksuite/editors/page-editor.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/editors/page-editor.ts) renders traditional linear documents. It initializes a `BlockStdScope` that wires the editor instance to the underlying block store, enabling nested block hierarchies and rich text editing.

```typescript
import { html, render } from 'lit';
import '@blocksuite/affine/blocksuite/editors/page-editor.js';

// Create a Yjs document (normally provided by the workspace)
const ydoc = new Y.Doc();
const store = new BlockStdScope({ store: ydoc });

// Render the editor
render(
  html`<page-editor .doc=${store} .specs=${[]}></page-editor>`,
  document.body
);

```

### EdgelessEditor for Canvas Layouts

The `EdgelessEditor` in [`packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts) provides a canvas-style surface where blocks can be positioned absolutely. It shares the same `BlockStdScope` architecture as the Page editor, ensuring block compatibility across modes while enabling free-form whiteboarding capabilities.

## Rich Block Library and Schema System

Every content element in the AFFiNE block-based editor is defined through a strict schema system located in `blocksuite/affine/model/src/blocks/*`.

### Block Flavours and Models

Each block declares a **flavour** (unique identifier), schema, and model class. For example, the paragraph block in [`paragraph-model.ts`](https://github.com/toeverything/AFFiNE/blob/main/paragraph-model.ts) defines its structure and validation rules:

```typescript
// From blocksuite/affine/model/src/blocks/paragraph/paragraph-model.ts
const ParagraphBlockSchema = defineBlockSchema({
  flavour: 'affine:paragraph',
  props: internal => ({
    text: internal.Text(),
    type: 'text' as ParagraphType,
  }),
  metadata: {
    version: 1,
    role: 'content',
    parent: ['affine:note', 'affine:database'],
  },
});

```

### Nested Block Relationships

Blocks declare valid parent/child relationships through the `parent` and `children` metadata fields. This enables structural validation—for example, ensuring tables can contain specific child blocks while maintaining document integrity. The table block in [`table-model.ts`](https://github.com/toeverything/AFFiNE/blob/main/table-model.ts) uses `metadata.isFlatData: true` to optimize storage for tabular data structures.

## Real-Time Collaboration via Yjs

The AFFiNE block-based editor leverages Yjs for conflict-free replicated data types (CRDTs). The `Workspace` entity in [`packages/frontend/core/src/modules/workspace/entities/workspace.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/modules/workspace/entities/workspace.ts) wraps a Yjs `Doc` and exposes `LiveData` streams that the UI subscribes to for reactive updates.

This architecture provides real-time multi-user synchronization, offline support, and automatic conflict resolution without manual intervention. All block operations—insertions, deletions, and updates—are transacted through the Yjs document to ensure consistency across clients.

## Extensibility and Customization

The editor exposes multiple extension points for customizing behavior and UI without modifying core source code.

### ViewExtension API

The `AffineEditorConfigViewExtension` in [`packages/frontend/core/src/blocksuite/view-extensions/editor-config/index.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/view-extensions/editor-config/index.ts) demonstrates how to inject custom functionality into both Page and Edgeless scopes. Extensions can register additional commands, toolbars, or UI widgets that appear automatically in both editing modes.

### Slash Menu Commands

The slash menu (`/`) provides context-aware block insertion. Each entry is a `SlashMenuConfig` that defines appearance conditions, icons, and execution logic. The table insertion configuration in [`blocksuite/affine/blocks/table/src/configs/slash-menu.ts`](https://github.com/toeverything/AFFiNE/blob/main/blocksuite/affine/blocks/table/src/configs/slash-menu.ts) shows how commands chain together to insert blocks and record telemetry:

```typescript
// From blocksuite/affine/blocks/table/src/configs/slash-menu.ts
std.command
  .chain()
  .pipe(getSelectedModelsCommand)
  .pipe(insertTableBlockCommand, {
    place: 'after',
    removeEmptyLine: true,
  })
  .run();

```

### Custom Toolbar Integration

The `EditorToolbar` component in [`blocksuite/affine/components/src/toolbar/toolbar.ts`](https://github.com/toeverything/AFFiNE/blob/main/blocksuite/affine/components/src/toolbar/toolbar.ts) provides formatting controls and block-insertion shortcuts. Developers can extend this LitElement-based component to add custom buttons that execute editor commands.

## Additional Core Capabilities

### Theme and Style Integration

Both `PageEditor` and `EdgelessEditor` consume the `ThemeProvider` service (`std.get(ThemeProvider)`) to support dynamic light/dark mode switching. This ensures consistent styling across all block types and UI chrome.

### Command Chain and Undo/Redo

All user actions are expressed as **commands** that can be chained, piped, and rolled back. The command system in `@blocksuite/affine/std` enables complex operations like slash-menu actions, toolbar formatting, and drag-and-drop reorganizations while maintaining a complete undo history.

## Summary

- **Dual editing modes**: The AFFiNE block-based editor provides both linear `PageEditor` and canvas-style `EdgelessEditor` components, sharing a common `BlockStdScope` architecture.
- **Schema-driven blocks**: Every content element is a typed block with defined flavours, parent/child relationships, and metadata (e.g., `isFlatData` for tables) located in `blocksuite/affine/model/src/blocks/*`.
- **Real-time collaboration**: Yjs-backed `Workspace` entities provide CRDT-based synchronization through `LiveData` streams in [`packages/frontend/core/src/modules/workspace/entities/workspace.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/modules/workspace/entities/workspace.ts).
- **Extensible architecture**: ViewExtensions, slash-menu configurations, and toolbar components allow custom functionality injection without core modifications.
- **Command-based operations**: All edits use chainable commands with full undo/redo support via the `@blocksuite/affine/std` system.

## Frequently Asked Questions

### What block types does the AFFiNE block-based editor support?

AFFiNE supports a comprehensive library of block types defined in `blocksuite/affine/model/src/blocks/*`, including paragraphs, headings, numbered and bulleted lists, tables, code blocks, images, embeds, and callouts. Each block declares a unique flavour (e.g., `affine:paragraph`, `affine:table`) and schema metadata that defines valid parent/child relationships, enabling complex nested structures while maintaining document integrity.

### How does AFFiNE handle real-time collaboration?

The editor leverages Yjs (Y-JavaScript) for conflict-free replicated data types (CRDTs). The `Workspace` entity in [`packages/frontend/core/src/modules/workspace/entities/workspace.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/modules/workspace/entities/workspace.ts) wraps a Yjs `Doc` and exposes `LiveData` reactive streams that the UI subscribes to. This architecture enables real-time multi-user synchronization, offline editing with automatic reconnection, and conflict resolution without manual merge intervention, as all block operations are transacted through the Yjs document.

### Can I extend AFFiNE's editor with custom blocks or UI components?

Yes, the AFFiNE block-based editor provides multiple extension points. You can create custom **ViewExtensions** (as demonstrated in [`packages/frontend/core/src/blocksuite/view-extensions/editor-config/index.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/view-extensions/editor-config/index.ts)) to inject commands, toolbar buttons, or UI widgets into both Page and Edgeless editors. Additionally, you can define custom **SlashMenuConfig** entries to add quick-insert commands, or extend the `EditorToolbar` component in [`blocksuite/affine/components/src/toolbar/toolbar.ts`](https://github.com/toeverything/AFFiNE/blob/main/blocksuite/affine/components/src/toolbar/toolbar.ts) to add formatting controls that execute custom command chains.

### What is the difference between Page and Edgeless editing modes?

**Page mode** (implemented in [`packages/frontend/core/src/blocksuite/editors/page-editor.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/editors/page-editor.ts)) provides a traditional linear document experience where blocks flow vertically in a reading order, similar to conventional word processors. **Edgeless mode** (in [`packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts)) offers a canvas-style whiteboard where blocks can be positioned absolutely anywhere, supporting free-form diagrams and spatial organization. Both modes share the same underlying `BlockStdScope` architecture and block definitions, ensuring content compatibility when switching between views.