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 --> UtilsWhat each layer does
- Foundations.
packages/stateis a small reactive signals library (Atom,Computed,Reactor, transactions).packages/state-reactadds React hooks.packages/utilsis the kitchen-sink helper library (collections, throttling, fractional indexing, version registry).packages/validateis a tiny runtime validator.packages/assetsships icons, fonts, and translations.packages/sync-coreis the protocol-level sync engine that runs on both client and server. - Core.
packages/storeis a reactive in-memory database of records, with computed indexes, side-effects, and a migration framework.packages/tlschemadefines the record types that make up a tldraw document — shapes, bindings, pages, instance state, presence, and assets — plus their migrations.packages/editorwires it all together: theEditorclass (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/tldrawre-exportseditorand adds the default shape utils, tools, bindings, UI components, menus, dialogs, and CSS.packages/syncis a thin React layer oversync-core(useSync,useSyncDemo). - Apps and templates consume the SDK.
apps/dotcomis the production tldraw.com app and its Cloudflare workers.apps/examplesis the development host foryarn dev.apps/docsis the marketing/docs site.apps/vscodeships 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| EditorCross-references
- For the package details see Packages.
- For app-level deployment shape see Apps and Deployment.
- For the cross-cutting topics see Features.
- For the most central types see Primitives.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.