Next release

This release adds custom record types to the store, 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.

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 CustomRecordInfo interface, createCustomRecordId(), createCustomRecordMigrationIds(), createCustomRecordMigrationSequence(), isCustomRecord(), isCustomRecordId() for custom record types. createTLSchema() and createTLStore() now accept a records option. (#8213)
  • Add @tldraw/editor-controller package with EditorController class for imperative editor control. (#7952)
  • Add handleSocketResume(), getSessionSnapshot(), and onSessionSnapshot to TLSocketRoom for WebSocket hibernation support. Add clientTimeout option to TLSyncRoom. (#8070)
  • Add Editor.getContainerDocument() and Editor.getContainerWindow() methods, and getOwnerDocument() / getOwnerWindow() helpers for cross-window embedding. (#8196)
  • Add useDirection() hook for RTL support. (#8033)
  • Add 'json' to TLCopyType for copying shapes as JSON in debug mode. (#8206)
  • Change TLSvgExportOptions.padding to accept number | '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 optional ease parameter. (#7977)

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)
  • Move the debug mode toggle into the preferences submenu. (#8259)

Bug fixes

  • Fix bailToMark silently 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)
Prev
v4.2.0
Next
v4.3.0