Next release
This release adds shape attribution with a new TLUserStore provider and extensible user records, clipboard hooks for intercepting copy, cut, and paste, custom record types to the store, a new @tldraw/mermaid package for converting Mermaid diagrams to native shapes, WebSocket hibernation support for tlsync, a new @tldraw/editor-controller package for scripting and automation, RTL language support in the UI, cross-window embedding support, and smarter export trimming. It also includes various other improvements and bug fixes.
What's new
Custom record types (#8213)
You can now register custom record types in the tldraw store for persisting and synchronizing domain-specific data that doesn't fit into shapes, bindings, or assets. Custom records support scoping (document/session/presence), validation, migrations, and default properties.
import { createTLSchema, createCustomRecordId } from 'tldraw'
const schema = createTLSchema({
records: [
{
typeName: 'marker',
scope: 'document',
validator: markerValidator,
},
],
})TypeScript module augmentation via TLGlobalRecordPropsMap lets custom record types participate in the TLRecord union.
WebSocket hibernation in tlsync (#8070)
TLSocketRoom now supports session resume and snapshot APIs for WebSocket hibernation environments like Cloudflare Durable Objects. Sessions can be suspended and restored without losing state, and the sync-cloudflare template has been updated to use the WebSocket Hibernation API.
New APIs include handleSocketResume() for restoring sessions from snapshots, getSessionSnapshot() for capturing session state, and an onSessionSnapshot callback for persisting snapshots to WebSocket attachments.
@tldraw/editor-controller (#7952)
A new @tldraw/editor-controller package provides an imperative API for driving the tldraw editor programmatically. EditorController wraps an Editor instance and exposes event dispatch, selection transforms, clipboard operations, and shape queries with fluent chaining.
import { EditorController } from '@tldraw/editor-controller'
const controller = new EditorController(editor)
controller.pointerMove(100, 100).pointerDown().pointerMove(200, 200).pointerUp()This is useful for scripting, automation, agent workflows, and REPL-style interaction. The release includes a Scripter example demonstrating the package.
RTL support (#8033)
The tldraw UI now supports right-to-left languages like Arabic. A new useDirection() hook returns 'ltr' or 'rtl' from the current translation context, and all Radix UI components and CSS have been updated to respect text direction. The dir attribute is set on .tl-container, and CSS uses logical properties (margin-inline-start, inset-inline-end, etc.) instead of physical ones.
@tldraw/mermaid (#8194, #8285, #8322)
A new @tldraw/mermaid package converts Mermaid diagram syntax into native tldraw shapes. Paste Mermaid text to create flowcharts, sequence diagrams, state diagrams, and mind maps as editable shapes on the canvas.
import { createMermaidDiagram } from '@tldraw/mermaid'
await createMermaidDiagram(
editor,
`
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]
`
)The package parses Mermaid syntax, extracts layout from the rendered SVG, and produces a diagram-agnostic blueprint that gets rendered into geo shapes, arrows, and groups.
Node creation is extensible: pass mapNodeToRenderSpec per diagram type to map diagram nodes to different shapes, or use createShape in BlueprintRenderingOptions to take full control of how nodes are created on the canvas.
Clipboard hooks (#8290)
New TldrawOptions hooks let you intercept and customize clipboard copy, cut, and paste. onBeforeCopyToClipboard filters or transforms serialized content before it hits the clipboard, onBeforePasteFromClipboard filters parsed paste payloads before shapes are created, and onClipboardPasteRaw handles raw clipboard data before tldraw's default paste pipeline.
const options: Partial<TldrawOptions> = {
onBeforeCopyToClipboard(info, content) {
// filter shapes or transform content before copy/cut
return content
},
onBeforePasteFromClipboard(info, content) {
// filter or transform parsed paste content
return content
},
onClipboardPasteRaw(info) {
// handle raw clipboard data yourself; return false to cancel tldraw handling
return false
},
}Shape attribution and TLUserStore (#8147)
A new shape attribution system tracks who created and last edited shapes. The TLUserStore provider interface connects tldraw to your auth system with reactive Signal-based methods: getCurrentUser() returns the active user for presence and attribution, while resolve(userId) resolves any user ID to display info.
<Tldraw
users={{
getCurrentUser: () => currentUserSignal,
resolve: (userId) => resolvedUserSignal(userId),
}}
/>User records are now document-scoped via the unified TLUser record type. SDK users can extend user records with custom validated metadata through createTLSchema:
const schema = createTLSchema({
user: {
meta: {
isAdmin: T.boolean,
department: T.string,
},
},
})Note shapes now track and display a "first edited by" attribution label in the bottom-right corner, showing who first added text to the note.
Cross-window embedding support (#8196)
Tldraw now works correctly when embedded in iframes, Electron pop-out windows, and Obsidian plugins where the global document and window differ from the ones tldraw is mounted in. All bare document and window references have been replaced with container-aware alternatives.
New helpers getOwnerDocument() and getOwnerWindow() are exported from @tldraw/editor, along with Editor.getContainerDocument() and Editor.getContainerWindow() convenience methods.
API changes
- Add
onBeforeCopyToClipboard,onBeforePasteFromClipboard, andonClipboardPasteRawhooks toTldrawOptionsfor intercepting clipboard operations. AddTLClipboardWriteInfoandTLClipboardPasteRawInfotypes. ExporthandleNativeOrMenuCopyfrom@tldraw/tldraw. (#8290) - Add
CustomRecordInfointerface,createCustomRecordId(),createCustomRecordMigrationIds(),createCustomRecordMigrationSequence(),isCustomRecord(),isCustomRecordId()for custom record types.createTLSchema()andcreateTLStore()now accept arecordsoption. (#8213) - Add
@tldraw/editor-controllerpackage withEditorControllerclass for imperative editor control. (#7952) - Add
handleSocketResume(),getSessionSnapshot(), andonSessionSnapshottoTLSocketRoomfor WebSocket hibernation support. AddclientTimeoutoption toTLSyncRoom. (#8070) - Add
Editor.getContainerDocument()andEditor.getContainerWindow()methods, andgetOwnerDocument()/getOwnerWindow()helpers for cross-window embedding. (#8196) - Add
useDirection()hook for RTL support. (#8033) - Add
'json'toTLCopyTypefor copying shapes as JSON in debug mode. (#8206) - Change
TLSvgExportOptions.paddingto acceptnumber | 'auto'. The'auto'mode (now default) renders with padding then trims to visual content bounds. (#8202) - Add
Vec.IsFinite()static method. (#8176) - Change
Vec.PointsBetween()to accept an optionaleaseparameter. (#7977) - Add
@tldraw/mermaidpackage withcreateMermaidDiagram(),renderBlueprint(), andMermaidDiagramErrorfor converting Mermaid syntax to tldraw shapes. (#8194) - Add
mapNodeToRenderSpecper-diagram-type option andcreateShapeoverride to@tldraw/mermaidfor customizing how diagram nodes are rendered as shapes. (#8322) - Add
TextManager.measureHtmlBatch()for batched DOM text measurement. (#7949) - Add
TLUserStoreinterface withgetCurrentUser()andresolve()for connecting tldraw to auth systems. Add unifiedTLUserrecord type,UserRecordType,createUserId,isUserId,userIdValidator, andcreateUserRecordType()for extensible user schemas. Adduserparameter tocreateTLSchema(). AddEditor.getAttributionUser(),Editor.getAttributionUserId(), andEditor.getAttributionDisplayName(). AddtextFirstEditedByprop toTLNoteShapeProps. (#8147)
Improvements
- Optimize geometry hot paths for hit testing: reduce allocations and function call overhead in
Vec,Edge2d,Circle2d,Arc2d,Polyline2d, and intersection routines. Circle hit testing is up to 19x faster, polyline nearest-point is 6.8x faster. (#8210) - Exports now automatically trim to visual content bounds, capturing overflow like thick strokes and arrowheads without extra whitespace. (#8202)
- Improve resize performance for multiple geo shapes with text labels by batching DOM measurements into a single pass per frame. (#7949)
- Move the debug mode toggle into the preferences submenu. (#8259)
Bug fixes
- Fix
bailToMarksilently discarding pending history changes when the target mark doesn't exist. (#8260) - Fix
FocusManager.dispose()not actually removing document event listeners due to.bind()creating new function references. (#8232) - Fix pasting into editable text shapes when the clipboard contains tldraw data. (#8192)
- Fix crash when isolating curved arrows with degenerate binding geometry. (#8176)
- Fix eraser not erasing shapes when starting a drag from inside a group's bounds. (#8054)
- Fix over-softened corners and end artifacts when shift-clicking to draw straight line segments. (#7977)
- Fix draw-shape loop-closing sensitivity so closing works more consistently across zoom levels. (#8293)
- Fix all shapes disappearing when a labeled arrow has zero length due to NaN propagation through the spatial index. (#8329)
- Fix "back to content" button flickering when both it and the "move focus to canvas" button are visible. (#8334) (contributed by @kaneel)