Open-Source Wikis

/

tldraw

/

tldraw

/

Architecture

tldraw/tldraw

Architecture

The tldraw repository is a layered system. At the bottom sit reactive primitives and a record store. On top of that sits a schema of records that describe the document. On top of that sits the editor — a large, observable state machine — and the React component layer that renders it. The full SDK adds default shapes, tools, bindings, and UI. Apps and templates consume the SDK.

Layered overview

graph TD
    subgraph Apps
        Dotcom[apps/dotcom]
        Examples[apps/examples]
        Docs[apps/docs]
        VSCode[apps/vscode]
        MCP[apps/mcp-app]
        Templates[templates/*]
    end

    subgraph SDK
        Tldraw[packages/tldraw]
        Sync[packages/sync]
    end

    subgraph Core
        Editor[packages/editor]
        Store[packages/store]
        TLSchema[packages/tlschema]
    end

    subgraph Foundations
        State[packages/state]
        StateReact[packages/state-react]
        Utils[packages/utils]
        Validate[packages/validate]
        Assets[packages/assets]
        SyncCore[packages/sync-core]
    end

    Apps --> SDK
    Apps --> Sync
    SDK --> Editor
    Sync --> SyncCore
    Sync --> Editor
    Editor --> Store
    Editor --> TLSchema
    Editor --> StateReact
    Store --> State
    TLSchema --> Store
    TLSchema --> Validate
    SyncCore --> Store
    SDK --> Utils
    Editor --> Utils

What each layer does

  • Foundations. packages/state is a small reactive signals library (Atom, Computed, Reactor, transactions). packages/state-react adds React hooks. packages/utils is the kitchen-sink helper library (collections, throttling, fractional indexing, version registry). packages/validate is a tiny runtime validator. packages/assets ships icons, fonts, and translations. packages/sync-core is the protocol-level sync engine that runs on both client and server.
  • Core. packages/store is a reactive in-memory database of records, with computed indexes, side-effects, and a migration framework. packages/tlschema defines the record types that make up a tldraw document — shapes, bindings, pages, instance state, presence, and assets — plus their migrations. packages/editor wires it all together: the Editor class (packages/editor/src/lib/editor/Editor.ts) owns the store, the camera, history, and a state-node tree of tools, and exposes a giant API for shapes, selection, and viewport.
  • SDK. packages/tldraw re-exports editor and adds the default shape utils, tools, bindings, UI components, menus, dialogs, and CSS. packages/sync is a thin React layer over sync-core (useSync, useSyncDemo).
  • Apps and templates consume the SDK. apps/dotcom is the production tldraw.com app and its Cloudflare workers. apps/examples is the development host for yarn dev. apps/docs is the marketing/docs site. apps/vscode ships the VS Code extension. templates/ contains starter projects.

The Editor

The single most important type is Editor in packages/editor/src/lib/editor/Editor.ts. It is more than 11,000 lines of code and is the public surface of the runtime API. It composes several managers (history, snap, click, focus, font, scribble, performance, spatial index, text, theme, tick, user preferences, edge scroll, inputs, collaborators) under packages/editor/src/lib/editor/managers/.

graph LR
    Editor --> Store
    Editor --> History[HistoryManager]
    Editor --> Snap[SnapManager]
    Editor --> SpatialIndex[SpatialIndexManager]
    Editor --> Inputs[InputsManager]
    Editor --> Click[ClickManager]
    Editor --> Tick[TickManager]
    Editor --> Theme[ThemeManager]
    Editor --> Font[FontManager]
    Editor --> RootStateNode[StateNode root]
    RootStateNode --> SelectTool[SelectTool]
    RootStateNode --> DrawTool[DrawTool]
    RootStateNode --> ArrowTool[ArrowTool]
    RootStateNode --> Other[other tools]

State machines live in StateNode instances. Tools are state nodes; each tool can have child states for idle, pointing, dragging, etc. The full selection-and-edit logic lives under packages/tldraw/src/lib/tools/SelectTool/ plus shared logic in packages/tldraw/src/lib/tools/selection-logic/.

Records and the store

Everything observable lives in the store as records. packages/tlschema/src/records/ defines the record types: TLShape, TLBinding, TLAsset, TLDocument, TLPage, TLPageState, TLInstance, TLInstancePresence, TLPointer, TLCamera, TLUser. Records are validated, migrated, and stored in packages/store/src/lib/Store.ts. Reads are reactive: editor.getCurrentPageShapes() returns an array that recomputes when the underlying records change.

sequenceDiagram
    participant UI as React component
    participant Editor as Editor
    participant Store as Store
    participant React as @tldraw/state

    UI->>Editor: editor.updateShape(...)
    Editor->>Store: store.put([record])
    Store->>React: signal mutated within transaction
    Store-->>Editor: side-effects fired
    React-->>UI: re-render via useValue(...)

Sync

Multiplayer is layered: packages/sync-core defines the wire protocol (packages/sync-core/src/lib/protocol.ts) and the room machinery (TLSyncRoom.ts, TLSyncClient.ts, TLSocketRoom.ts). packages/sync exposes useSync() and useSyncDemo() hooks that connect a tldraw store to a TLSocketRoom over WebSocket. The production server lives at apps/dotcom/sync-worker/, which runs TLSocketRoom inside a Cloudflare Durable Object and persists snapshots to R2.

tldraw.com (apps/dotcom)

The production app (apps/dotcom/client) is a React Router single-page app with a "tla" (tldraw app) area for signed-in users (folders, sharing, file management) and an anonymous editor route. It talks to four workers:

  • apps/dotcom/sync-worker — the realtime collaboration server.
  • apps/dotcom/asset-upload-worker — handles uploaded asset blobs.
  • apps/dotcom/image-resize-worker — serves resized image variants.
  • apps/dotcom/tldrawusercontent-worker — serves user-generated content.
  • apps/dotcom/zero-cache — the Zero view-syncer for app-state replication backed by Postgres.

Repo-level orchestration

lazy.config.ts declares the lazyrepo task graph (build, refresh-assets, build-api, build-i18n, e2e). Every package wires its scripts through lazy run <task>. The shared TypeScript and ESLint configs live under internal/config/ and the build tooling (build-package.ts, build-api.ts, prepack.ts) under internal/scripts/.

Diagrams of the major data flows

Pointer → tool → store

graph TD
    Browser[browser pointer event]
    Browser -->|onPointerDown| Editor
    Editor --> InputsManager
    InputsManager --> Editor
    Editor -->|dispatch event| Root[Root StateNode]
    Root --> CurrentTool[Current tool's state node]
    CurrentTool -->|editor.createShape, etc.| Editor
    Editor --> Store
    Store --> SideEffects
    Store --> Reactive[reactive subscribers]
    Reactive --> React[React components]

Shape lifecycle

graph LR
    User -->|drag| Tool
    Tool -->|createShape| Editor
    Editor -->|put record| Store
    ShapeUtil -->|render| Canvas
    User -->|drag handle| Tool
    Tool -->|updateShape| Editor
    Editor -->|put record| Store
    ShapeUtil -->|geometry, hit-test, snap| Editor

Cross-references

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – tldraw wiki | Factory