tldraw Release Notes
26 release notes curated from 1 source by the Releasebot Team. Last updated: May 6, 2026
- May 6, 2026
- Date parsed from source:May 6, 2026
- First seen by Releasebot:May 6, 2026
v5.0.0
tldraw ships its fifth major SDK release with a new theme system, customizable canvas overlays, custom records, extensible asset and geo shape APIs, frame-like custom shapes, and user attribution. It also adds driver and Mermaid packages, clipboard hooks, RTL support, cross-window embedding, and performance tools.
Welcome to the release for the tldraw SDK's fifth major release.
This 5.0 release introduces several new systems and long-requested features, including:
a first-class theme system with display values
a new OverlayUtil system for canvas overlays
an extensibility APIs for custom record types, asset types, geo shapes, and frame-like shapesTwo new packages ship alongside it: @tldraw/driver for imperative editor control and @tldraw/mermaid for converting Mermaid diagrams into native shapes. It also adds a shape attribution system with the TLUserStore provider, clipboard and performance-measurement hooks, WebSocket hibernation in tlsync, RTL support, cross-window embedding, and various other improvements and bug fixes.
Migrate with agents!
We've created a new experimental agent skill that you can use to migrate tldraw automatically. To use the skill, run your agent harness of choice (e.g. Claude Code or Codex) at the directory containing your tldraw project, and ask it to:
Please visit https://github.com/tldraw/tldraw/blob/main/skills/tldraw-migrate/README.md and follow the instructions there to migrate tldraw to the latest version.
The skill will copy the necessary files into your directory, pull the relevant release notes and migration guides from tldraw, and then get to work on the migration. We've seen good results in the repos that we've tried it on, however agents are still is a bit of a dice roll so please check their work.What's new in 5.0
π₯ Custom themes with display values (#8410)
A new first-class theme system replaces the previous approach where colors were hardcoded and resolved inline. Themes are named, registered objects that shape utils consume via a structured display values pipeline.
Register custom themes via TLThemes module augmentation for type-safe IDs, add or remove palette colors via TLThemeDefaultColors and TLRemovedDefaultThemeColors, and pass themes to the editor via the themes and initialTheme props:
<Tldraw themes={{ corporate: myCorporateTheme }} initialTheme="corporate" />Each default shape util defines getDefaultDisplayValues to resolve visual properties (colors, stroke widths, font sizes) from the current theme and color mode. Override display values for a default shape with getCustomDisplayValues:
const MyDrawShapeUtil = DrawShapeUtil.configure({ getCustomDisplayValues(_editor, _shape, _theme, _colorMode) { return { strokeWidth: 10 } }, })Migration guide
The inferDarkMode prop has been renamed to colorScheme and changed from boolean to 'light' | 'dark' | 'system':
// Before <Tldraw inferDarkMode /> // After <Tldraw colorScheme="system" />useIsDarkMode() has been renamed to useColorMode() and returns 'dark' | 'light' instead of boolean.
getDefaultColorTheme() and DefaultColorThemePalette have been removed. Use editor.getCurrentTheme().colors[colorMode] instead:
// Before const theme = getDefaultColorTheme({ isDarkMode }) // After const theme = editor.getCurrentTheme() const colors = theme.colors[editor.getColorMode()]useDefaultColorTheme() has been removed. Use editor.getCurrentTheme() and useColorMode() instead.
FONT_FAMILIES, FONT_SIZES, LABEL_FONT_SIZES, STROKE_SIZES, TEXT_PROPS, and ARROW_LABEL_FONT_SIZES have been removed β these are now resolved via display values.
SvgExportContext.themeId has been renamed to SvgExportContext.colorMode and changed from string to 'light' | 'dark'.
getColorValue() now takes TLThemeColors as its first argument instead of TLDefaultColorTheme.
π₯ Custom overlays (#8469)
A new OverlayUtil system unifies canvas overlays. Built-in brushes, handles, scribbles, snap indicators, selection foreground, collaborator cursors, and arrow hints are now implemented as overlay utils and can be customized or extended via the overlayUtils prop.
Overlays now render in front of indicators in a unified CanvasOverlays layer, and the legacy default component implementations and their CSS have been removed.
Migration guide
The legacy overlay components have been removed from TLEditorComponents. Canvas overlays are now rendered directly to a 2D canvas context by OverlayUtil classes, and customization happens by subclassing the default util rather than swapping a React component.
Customizing a built-in overlay: extend the default util and give it the same static type; it replaces the built-in when passed via overlayUtils:
// Before const components: TLEditorComponents = { Brush: MyBrushComponent, } <Tldraw components={components} /> // After import { Tldraw, BrushOverlayUtil, type TLBrushOverlay } from 'tldraw' class MyBrushOverlayUtil extends BrushOverlayUtil { override render(ctx: CanvasRenderingContext2D, overlays: TLBrushOverlay[]) { const overlay = overlays[0] if (!overlay) return const { x, y, w, h } = overlay.props const zoom = this.editor.getEfficientZoomLevel() ctx.fillStyle = 'rgba(0, 0, 255, 0.1)' ctx.strokeStyle = 'blue' ctx.lineWidth = 1 / zoom ctx.beginPath() ctx.rect(x, y, w, h) ctx.fill() ctx.stroke() } } <Tldraw overlayUtils={[MyBrushOverlayUtil]} />The following slots have been removed from TLEditorComponents. Subclass the matching default overlay util instead:
Brush, ZoomBrush β BrushOverlayUtil, ZoomBrushOverlayUtil
Scribble β ScribbleOverlayUtil
SnapIndicator β SnapIndicatorOverlayUtil
Handle, Handles β ShapeHandleOverlayUtil
SelectionForeground, SelectionBackground β SelectionForegroundOverlayUtil
CollaboratorHint β CollaboratorHintOverlayUtil (collaborator cursors/brushes/scribbles have their own utils too)The corresponding Default* exports and LiveCollaborators have also been removed.
ShapeUtil.indicator() β ShapeUtil.getIndicatorPath(). The shape util method that draws a shape's selection indicator no longer returns JSX. It now returns a Path2D (or a TLIndicatorPath) and is composited onto the canvas overlay layer. Every custom ShapeUtil that overrides indicator needs to migrate:
// Before override indicator(shape: MyShape) { return <rect width={shape.props.w} height={shape.props.h} /> } // After override getIndicatorPath(shape: MyShape): Path2D { const path = new Path2D() path.rect(0, 0, shape.props.w, shape.props.h) return path }For shapes whose indicator should match the shape's geometry, return path built from the same primitives. For placeholder shapes (e.g. an ErrorShape), return an empty new Path2D() and declare the return type explicitly so TypeScript doesn't infer never.
The React component slots ShapeIndicator, ShapeIndicators, and ShapeIndicatorErrorFallback are also removed from TLEditorComponents β there is no replacement React slot, since indicators now render through getIndicatorPath().
Overlays cannot render React; they draw into a 2D canvas context. For React-based overlays, render an HTML layer above the canvas via a regular TLEditorComponents slot like InFrontOfTheCanvas.
Overlay colors come from TLTheme, not CSS variables. Read them inside render() via this.editor.getCurrentTheme().colors[this.editor.getColorMode()] so overlays follow light/dark mode automatically.
These CSS variables have been removed: --tl-color-snap, --tl-color-brush-fill, --tl-color-brush-stroke, --tl-color-laser, --tl-layer-overlays-custom.
Overriding them (or the removed selectors .tl-brush, .tl-scribble, .tl-snap-indicator, .tl-handle*, .tl-selection__fg__outline, .tl-corner-handle, .tl-text-handle, .tl-corner-crop-handle, .tl-mobile-rotate__*) no longer has any effect. Port these to TLTheme entries or to the overlay util's render().
π₯ Tldraw component options
Several props that previously lived at the top level have been consolidated under the options prop, and a few extension points moved off the component entirely.
Migration guide
cameraOptions, textOptions, and deepLinks props moved into options:
// Before <Tldraw cameraOptions={camOpts} textOptions={txtOpts} deepLinks /> // After <Tldraw options={{ camera: camOpts, text: txtOpts, deepLinks: true }} />embeds prop removed; configure embed definitions on EmbedShapeUtil:
// Before <Tldraw embeds={[...DEFAULT_EMBED_DEFINITIONS, customEmbed]} /> // After import { EmbedShapeUtil } from 'tldraw' const ConfiguredEmbedShapeUtil = EmbedShapeUtil.configure({ embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, customEmbed], }) <Tldraw shapeUtils={[ConfiguredEmbedShapeUtil]} />This silently compiles in some setups (the prop is unknown but JSX won't always reject it), so this is the kind of change that the typecheck won't always catch β search the source for embeds= and migrate any matches.
setDefaultEditorAssetUrls() and setDefaultUiAssetUrls() are no longer part of the public API. They still exist at runtime for now, but they're marked @internal and should not be relied on. Pass assetUrls to each instead:
// Before import { setDefaultEditorAssetUrls, setDefaultUiAssetUrls } from 'tldraw' setDefaultEditorAssetUrls(assetUrls) setDefaultUiAssetUrls(assetUrls) <Tldraw /> // After <Tldraw assetUrls={assetUrls} />Do not reach for module augmentation to re-expose the old globals β find each mount point and pass assetUrls directly.
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.
Extensible geo shapes (#8543)
GeoShapeUtil now supports custom geo types via a customGeoTypes option on configure(). Previously, adding a new geo type required forking or monkey-patching GeoShapeUtil; now custom types plug into the existing system and inherit labels, resizing, fill/dash/color styling, SVG export, and hyperlink support β while supplying their own path geometry, snap behavior, creation size, and style panel icon.
import { GeoShapeUtil, PathBuilder } from 'tldraw' const MyGeoShapeUtil = GeoShapeUtil.configure({ customGeoTypes: { 'rounded-rect': { getPath: (w, h, shape) => { const r = Math.min(w, h) * 0.2 return new PathBuilder() .moveTo(r, 0, { geometry: { isFilled: shape.props.fill !== 'none' } }) .lineTo(w - r, 0) .circularArcTo(r, false, true, w, r) .lineTo(w, h - r) .circularArcTo(r, false, true, w - r, h) .lineTo(r, h) .circularArcTo(r, false, true, 0, h - r) .lineTo(0, r) .circularArcTo(r, false, true, r, 0) .close() }, snapType: 'polygon', icon: 'geo-rectangle', defaultSize: { w: 200, h: 150 }, }, }, }) <Tldraw shapeUtils={[MyGeoShapeUtil]} />Each definition supplies getPath, snapType ('polygon' for vertex + center snaps, 'blobby' for center-only), and an icon for the style panel, plus optional defaultSize and onDoubleClick handler. Custom types appear alongside the built-ins in the geo picker.
π₯ Extensible asset types (#8031)
A new AssetUtil base class follows the ShapeUtil / BindingUtil pattern, making the asset system extensible. Previously, assets were a hardcoded union of image, video, and bookmark types. Now you can register custom asset types with their own MIME type handling, file-to-asset conversion, and shape creation logic.
import { AssetUtil } from '@tldraw/editor' class AudioAssetUtil extends AssetUtil<TLAudioAsset> { static override type = 'audio' as const override getSupportedMimeTypes() { return ['audio/mpeg', 'audio/wav'] } override async getAssetFromFile(editor, file) { /* ... */ } } ;<Tldraw assetUtils={[AudioAssetUtil]} />TLAssetStore keeps upload/resolve/remove as cross-cutting concerns, while AssetUtil handles type-specific behavior: MIME types, file-to-asset metadata, and asset-to-shape creation.
Migration guide
assetValidator has been removed. Use imageAssetValidator, videoAssetValidator, or bookmarkAssetValidator instead.
getMediaAssetInfoPartial has been removed. Use AssetUtil.getAssetFromFile instead.
notifyIfFileNotAllowed signature changed from (file, options) to (editor, file, options).
getAssetInfo signature changed from (file, options, assetId?) to (editor, file, assetId?) and now returns TLAsset | null instead of throwing.
Frame-like custom shapes (#8331)
Custom shapes can now opt into frame-like behavior: clipping children, acting as a parent on paste and drag-in, blocking erasure from inside, and supporting full-brush selection. Previously, frame behavior was hardcoded to the built-in frame type; the editor and tools now route frame checks through editor.getShapeUtil(shape).isFrameLike(shape).
The easiest way to build one is to extend the new BaseFrameLikeShapeUtil abstract class, which provides sensible defaults for isFrameLike, providesBackgroundForChildren, canReceiveNewChildrenOfType, canRemoveChildrenOfType, getClipPath, onDragShapesIn, and onDragShapesOut:
import { BaseFrameLikeShapeUtil, SVGContainer } from '@tldraw/editor' class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> { static override type = 'my-container' as const static override props = myContainerShapeProps override getDefaultProps() { return { w: 300, h: 200 } } override component(shape: MyContainerShape) { return <SVGContainer>...</SVGContainer> } override indicator(shape: MyContainerShape) { return <rect width={shape.props.w} height={shape.props.h} /> } }Shapes that need frame behavior without inheriting from BaseFrameLikeShapeUtil can override isFrameLike() directly on any ShapeUtil. FrameShapeUtil now extends BaseFrameLikeShapeUtil, so existing frames are unaffected.
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.
Migration guide
useTldrawUser has been removed. Previously it bundled user preferences (color, color scheme) and user identity (id, name) into a single object. These are now separate concerns:
Preferences (color, color scheme, snap mode, etc.) are managed via TLUserPreferences. Set them with the editor's user-preferences API directly.
Identity for attribution comes from a TLUserStore provider passed to the new users prop on .
// Before
const user = useTldrawUser({ userPreferences, setUserPreferences })
// After
<Tldraw
users={{
getCurrentUser: () => currentUserSignal,
resolve: (userId) => resolvedUserSignal(userId),
}}
colorScheme={userPreferences.colorScheme}
/>If you only need preferences (no custom identity), pass colorScheme directly and seed preferences imperatively after mount; if you only need identity, pass users. Most apps want both. #### @tldraw/driver (#7952) A new @tldraw/driver package provides an imperative API for driving the tldraw editor programmatically. Driver wraps an Editor instance and exposes event dispatch, selection transforms, clipboard operations, and shape queries with fluent chaining. ```ts import { Driver } from '@tldraw/driver' const driver = new Driver(editor) driver.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 that demonstrates the package.
@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.
Arbitrary iframe embeds (#8306)
Paste any embed code onto the canvas to create an embed shape. Previously only URLs matching known providers (YouTube, Google Maps, etc.) worked. Now any valid iframe with an HTTP(S) source creates an embed directly. It works with services like OpenStreetMap, SoundCloud, Loom, and more.
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 }, }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.
Performance measurement hooks (#8421)
A new editor.performance API lets SDK users subscribe to editor performance events: interaction frame times (translating, resizing, drawing), camera pan/zoom sessions, shape create/update/delete operations, and undo/redo timings. Events aggregate frame-time percentiles (avg / median / p95 / p99) and, on Chromium, attach Long Animation Frame data with per-script attribution for diagnosing jank.
const unsubscribe = editor.performance.on('interaction-end', (event) => { // forward to analytics const { frameTimes, longAnimationFrames, ...metrics } = event track('editor.interaction', metrics) })The manager is zero-overhead when no listeners are attached. Custom tools opt in to interaction tracking via static trackPerformance = true on the StateNode. A PerformanceApiAdapter is also available for piping events into the browser Performance API for DevTools timelines.
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.
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.
API changes
- π₯ Remove defaultColorNames, DefaultColorThemePalette, DefaultLabelColorStyle, TLDefaultColorTheme type, and getDefaultColorTheme() from @tldraw/tlschema. Use editor.getCurrentTheme().colors instead. (#8410)
- π₯ Remove ARROW_LABEL_FONT_SIZES, FONT_FAMILIES, FONT_SIZES, LABEL_FONT_SIZES, STROKE_SIZES, TEXT_PROPS, and useDefaultColorTheme() from @tldraw/tldraw. These are now resolved via display values. (#8410)
- π₯ Rename inferDarkMode prop to colorScheme (boolean β 'light' | 'dark' | 'system'). Rename useIsDarkMode() to useColorMode() (returns 'dark' | 'light' instead of boolean). (#8410)
- π₯ Rename SvgExportContext.themeId to SvgExportContext.colorMode (string β 'light' | 'dark'). (#8410)
- π₯ Change getColorValue() first argument from TLDefaultColorTheme to TLThemeColors. (#8410)
- π₯ Change PlainTextLabelProps and RichTextLabelProps: font β fontFamily, align β textAlign, fill removed. (#8410)
- π₯ Remove Brush, ZoomBrush, Scribble, SnapIndicator, Handle, Handles, SelectionForeground, SelectionBackground, CollaboratorHint, ShapeIndicator, ShapeIndicators, and ShapeIndicatorErrorFallback from TLEditorComponents, along with the corresponding Default* component exports and LiveCollaborators. Migrate custom overlay components to OverlayUtil; indicator customization moves to ShapeUtil.getIndicatorPath(). (#8469)
- π₯ Remove the CSS variables --tl-color-snap, --tl-color-brush-fill, --tl-color-brush-stroke, --tl-color-laser, and --tl-layer-overlays-custom, along with the overlay class selectors (.tl-brush, .tl-scribble, .tl-snap-indicator, .tl-handle*, .tl-selection__fg__outline, .tl-corner-handle, .tl-text-handle, .tl-corner-crop-handle, .tl-mobile-rotate__*). Overlay colors now come from TLTheme. (#8469)
- π₯ Remove assetValidator. Use imageAssetValidator, videoAssetValidator, or bookmarkAssetValidator instead. (#8031)
- π₯ Remove getMediaAssetInfoPartial. Use AssetUtil.getAssetFromFile instead. (#8031)
- π₯ Change notifyIfFileNotAllowed signature from (file, options) to (editor, file, options). (#8031)
- π₯ Change getAssetInfo signature from (file, options, assetId?) to (editor, file, assetId?) and return TLAsset | null instead of throwing. (#8031)
- π₯ Move the Cmd+Shift+C / Ctrl+Shift+C shortcut from "Copy as SVG" to "Copy as PNG". (#8532)
- π₯ Replace ShapeUtil.indicator() (returned JSX) with ShapeUtil.getIndicatorPath() (returns Path2D | TLIndicatorPath | undefined). Indicators now render to the canvas overlay layer instead of as React elements. (#8469)
- π₯ Move cameraOptions, textOptions, and deepLinks from top-level props into the options prop (e.g. options={{ camera, text, deepLinks }}).
- π₯ Remove the embeds prop from . Configure embed definitions via EmbedShapeUtil.configure({ embedDefinitions }) and pass the configured util through shapeUtils.
- π₯ Demote setDefaultEditorAssetUrls() and setDefaultUiAssetUrls() to @internal. Pass assetUrls to each instead.
- π₯ Remove useTldrawUser. Use the new users prop (TLUserStore) for identity and TLUserPreferences for preferences.
- π₯ Replace TLDrawShapeSegment.points with the helper getPointsFromDrawSegment(segment, scaleX, scaleY) so segment points respect the shape's current scale.
- π₯ Change BindingUtil hook params: fromShapeType/toShapeType are removed in favor of full fromShape/toShape records (read fromShape.type / toShape.type directly).
- π₯ Add 'middle-legacy' (and other legacy values) to the align union resolved by PlainTextLabel/RichTextLabel. If your code maps align into PlainTextLabel.textAlign, narrow legacy values to one of 'start' | 'center' | 'end' before passing them through.
- Add TLTheme, TLThemeId, TLThemes, TLThemeDefaultColors, TLThemeColors, TLRemovedDefaultThemeColors, ThemeManager, getDisplayValues(), getColorValue(), and DEFAULT_THEME for the new theme system. (#8410)
- Add themes and initialTheme props to and . (#8410)
- Add getCurrentTheme(), setCurrentTheme(), getThemes(), getTheme(), updateTheme(), updateThemes(), and getColorMode() to the editor. (#8410)
- Add getDefaultDisplayValues and getCustomDisplayValues to shape util options for theme-aware visual properties. (#8410)
- Add OverlayUtil, TLOverlay, OverlayManager, Editor.overlays, defaultOverlayUtils, and the overlayUtils prop on / for registering and managing canvas overlays. (#8469)
- Add per-overlay utils: BrushOverlayUtil, ZoomBrushOverlayUtil, ScribbleOverlayUtil, SnapIndicatorOverlayUtil, ShapeHandleOverlayUtil, SelectionForegroundOverlayUtil, ArrowHintOverlayUtil, CollaboratorBrushOverlayUtil, CollaboratorScribbleOverlayUtil, CollaboratorHintOverlayUtil, and CollaboratorCursorOverlayUtil. (#8469)
- Add overlay-related colors to TLTheme. (#8469)
- Add BaseFrameLikeShapeUtil abstract class and ShapeUtil.isFrameLike() so custom shapes can opt into frame-like behaviors (paste parenting, full-brush selection, blocking erasure from inside, clipping children). FrameShapeUtil now extends BaseFrameLikeShapeUtil. (#8331)
- Add extensibility API for custom geo shape types via GeoShapeUtil.configure({ customGeoTypes }). Add GeoTypeDefinition interface for defining path geometry, snap behavior, creation size, style panel icon, and double-click handler while inheriting standard geo shape behavior. (#8543)
- Add AssetUtil base class with configure(), getDefaultProps(), getSupportedMimeTypes(), getAssetFromFile(), and createShape(). Add assetUtils prop to . Add Editor.getAssetUtil(), Editor.hasAssetUtil(), and Editor.getAssetUtilForMimeType(). Add TLGlobalAssetPropsMap for type-safe custom asset registration. Add createAssetRecordType(), defaultAssetSchemas, and assets parameter to createTLSchema(). (#8031)
- Add CustomRecordInfo interface, createCustomRecordId(), createCustomRecordMigrationIds(), createCustomRecordMigrationSequence(), isCustomRecord(), isCustomRecordId() for custom record types. createTLSchema() and createTLStore() now accept a records option. (#8213)
- Add TLUserStore interface with getCurrentUser() and resolve() for connecting tldraw to auth systems. Add unified TLUser record type, UserRecordType, createUserId, isUserId, userIdValidator, and createUserRecordType() for extensible user schemas. Add user parameter to createTLSchema(). Add Editor.getAttributionUser(), Editor.getAttributionUserId(), and Editor.getAttributionDisplayName(). Add textFirstEditedBy prop to TLNoteShapeProps. (#8147)
- Add @tldraw/driver package with Driver class for imperative editor control. (#7952)
- Add @tldraw/mermaid package with createMermaidDiagram(), renderBlueprint(), and MermaidDiagramError for converting Mermaid syntax to tldraw shapes. (#8194)
- Add mapNodeToRenderSpec per-diagram-type option and createShape override to @tldraw/mermaid for customizing how diagram nodes are rendered as shapes. (#8322)
- Add onBeforeCopyToClipboard, onBeforePasteFromClipboard, and onClipboardPasteRaw hooks to TldrawOptions for intercepting clipboard operations. Add TLClipboardWriteInfo and TLClipboardPasteRawInfo types. Export handleNativeOrMenuCopy from @tldraw/tldraw. (#8290)
- Add Cmd+Shift+V / Ctrl+Shift+V shortcut to paste clipboard content as plain text. Cmd+Shift+V no longer toggles paste-at-cursor positioning. (#8347)
- Add support for pasting arbitrary embed codes to create embed shapes from any service. (#8306)
- Add Canva to DEFAULT_EMBED_DEFINITIONS so Canva design URLs paste as iframe embeds. (#8459)
- Add TldrawOptions.rightClickPanning (default true) to gate the new right-click drag-to-pan behavior. Add InputsManager.getIsRightPointing() / setIsRightPointing(). (#8501)
- Add Editor.performance (PerformanceManager) with on(), once(), and dispose() for subscribing to interaction, camera, shape-operation, frame, and undo/redo performance events. Add PerformanceApiAdapter for piping events into the browser Performance API. Add StateNode.trackPerformance static field so custom tools can opt into interaction tracking. Add TLPerfFrameTimeStats, TLPerfEventMap, TLInteractionStartPerfEvent, TLInteractionEndPerfEvent, TLCameraStartPerfEvent, TLCameraEndPerfEvent, TLShapeOperationPerfEvent, TLFramePerfEvent, TLUndoRedoPerfEvent, TLPerfLongAnimationFrame, and TLPerfLongAnimationFrameScript. (#8421)
- Add Editor.getContainerDocument() and Editor.getContainerWindow() methods, and getOwnerDocument() / getOwnerWindow() helpers for cross-window embedding. (#8196)
- Add handleSocketResume(), getSessionSnapshot(), and onSessionSnapshot to TLSocketRoom for WebSocket hibernation support. Add clientTimeout option to TLSyncRoom. (#8070)
- Add useDirection() hook for RTL support. (#8033)
- Expose PeopleMenu, PeopleMenuItem, PeopleMenuFacePile, and UserPresenceEditor through TldrawUiComponents so the share panel sub-components can be overridden via the components prop. (#8346)
- Add 'none' to TLDefaultDashStyle for hiding shape borders while preserving fills. The option is not exposed in the style panel β it can only be set programmatically. Add NonePathBuilderOpts; PathBuilder.toSvg() now returns null for the 'none' style. (#8453)
- Export defaultHandleExternalFileReplaceContent, which was previously referenced in docs but missing from the package's public exports. (#8579)
- Change TLSvgExportOptions.padding to accept number | 'auto'. The 'auto' mode (now default) renders with padding then trims to visual content bounds. (#8202)
- Add TextManager.measureHtmlBatch() for batched DOM text measurement. (#7949)
- Add 'json' to TLCopyType for copying shapes as JSON in debug mode. (#8206)
- Add Vec.IsFinite() for checking whether a vector has finite coordinates. (#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)
- Improve resize performance for multiple geo shapes with text labels by batching DOM measurements into a single pass per frame. (#7949)
- Exports now automatically trim to visual content bounds, capturing overflow like thick strokes and arrowheads without extra whitespace. (#8202)
- Add Cmd+Shift+V / Ctrl+Shift+V shortcut to paste clipboard content as plain text, stripping HTML and rich formatting. Cmd+Shift+V no longer toggles paste-at-cursor positioning β the "Paste at cursor" preference covers that use case. (#8347)
- Change Cmd+Shift+C / Ctrl+Shift+C to copy selected shapes as PNG instead of SVG, matching the default in most apps. The SVG copy path is still available from the Copy-as menu. (#8532)
- Right-click and drag now pans the camera, matching tools like Figma. A static right-click still opens the context menu on release. Gate via TldrawOptions.rightClickPanning (defaults to true). (#8501)
- Allow Cmd+click / Ctrl+click on style panel items to apply style changes only to selected shapes without updating defaults for future shapes. (#8452)
- Hide selection overlay when nudging shapes with arrow keys. (#8447)
- Move the debug mode toggle into the preferences submenu. (#8259)
- Tighten iframe referrer policy for embeds to send only the origin instead of the full URL to third-party embed providers. (#8412)
- Replace the @use-gesture/react dependency with custom gesture handling. Bundle size is smaller and a stale dependency is gone. (#8392)
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 camera state getting stuck at 'moving' when the editor is disposed mid-transition (e.g. deep links under React strict mode), which blocked all pointer interactions on the canvas. (#8396)
- Fix leaked camera animations, following subscriptions, and stale menu state when the editor is disposed during active operations. (#8422)
- Fix a memory leak where disposed Editor instances were retained through a shared throttled updateHoveredShapeId closure. (#8439)
- Fix a memory leak where disposed Editor instances were retained through the window.__tldraw__hardReset global. (#8476)
- Fix crash when isolating curved arrows with degenerate binding geometry. (#8176)
- Fix all shapes disappearing when a labeled arrow has zero length (both endpoints overlapping), which previously propagated NaN values through the spatial index. (#8329)
- Fix crash from duplicate fractional index keys in kickoutOccludedShapes during multiplayer sync. (#8448)
- Fix crash in browsers without OffscreenCanvas support (e.g. older Safari) when preloading image alpha data. (#8582)
- Fix over-softened corners and end artifacts when shift-clicking to draw straight line segments. (#7977)
- Fix eraser not erasing shapes when starting a drag from inside a group's bounds. (#8054)
- Fix draw-shape loop-closing sensitivity so closing works more consistently across zoom levels. (#8293)
- Fix shadow artifact and oversized caret on notes and geo shapes at high zoom in dynamic size mode. (#8378)
- Fix pattern fill not scaling correctly with dynamic size mode. (#8441)
- Fix arrow labels rendered at incorrect size when editing at high zoom in dynamic size mode. (#8451)
- Fix rotation performance regression where CSS transform: scale() was applied to shape labels even when dynamic size was inactive, forcing an unnecessary compositing layer on every shape. (#8570)
- Fix spiky artifacts on draw-style geometric shapes by using a circular random offset distribution instead of a square one. (#8466)
- Fix pasting into editable text shapes when the clipboard contains tldraw data. (#8192)
- Fix slight positioning drift when pasting text onto the canvas. (#8345)
- Fix right-clicking inside a multi-selection over a filled background shape changing the selection. (#8434)
- Fix context menu briefly flashing when right-clicking to dismiss an already-open context menu. (#8498)
- Fix context menu items accidentally being selected when right-clicking and quickly moving the mouse. (#8499)
- Fix opacity slider drag not working on Safari due to stopPropagation blocking pointer events. (#8519)
- Fix quick zoom mode (Z + Shift) cursor anchor and brush misalignment when the tldraw container is offset from the top-left of the window. (#8520)
- Fix Cmd+Shift+V doing nothing when the clipboard contains an image; the shortcut now falls back to the regular paste flow when there is no plain-text payload. (#8490)
- Fix keyboard shortcuts that change styles not marking the change as a style update, causing subsequent shapes to use the wrong defaults. (#8599) (contributed by @danieljamesross)
- Fix "back to content" button flickering when both it and the "move focus to canvas" button are visible. (#8334)
- Fix intermittent ResizeObserver loop browser warnings when hovering over toolbar buttons. (#8574)
- Fix missing sandbox attribute on GitHub Gist embeds. (#8403)
- Restrict sandbox permissions for unknown/arbitrary embeds to mitigate security risks from untrusted content. (#8404)
- Fix duplicate presence cursors appearing after login or logout in multiplayer sessions by filtering a session's own stale record from the connect handshake response. (#8542)
- Fix sticky notes preserving stale attribution when cloned via a nib. (#8614)
- Fix Cmd+V paste of tldraw-copied PNGs on Chrome 147 stable, where the custom-format clipboard entry returned a 0-byte blob. (#8615)
- Fix note author attribution overflowing the sticky note width in SVG and PNG exports. (#8664)
- Fix keyboard shortcuts not matching on alternative Latin keyboard layouts (Dvorak, Colemak, AZERTY) by replacing the hotkeys-js library with a small layout-aware native event matcher. (#8669)
- Fix TypeScript signature mismatch on ShapeUtil base-class methods so overrides that need a shape parameter can declare it without errors. (#8521)
- Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:May 1, 2026
v4.5.2
tldraw updates release notes for the next release with bug fixes.
Generated from commits between v4.5.2 and HEAD
Bug Fixes
docs: update release notes for next release (#8270) (apps/docs)
Original source All of your release notes in one feed
Join Releasebot and get updates from tldraw and hundreds of other software products.
- Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.8
tldraw fixes a memory leak in shared throttled hover state updates.
Generated from commits between v4.5.8 and HEAD
Bug fixes
fix(editor): fix memory leak from shared throttled updateHoveredShapeId (#8474)
Original source - Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.7
tldraw fixes the VS Code extension by excluding hotkeys-js from the bundle.
Generated from commits between v4.5.7 and HEAD
fix(vscode): hotfix exclude hotkeys-js from extension bundle (#8444) (apps/vscode)
Original source - Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.6
tldraw fixes crashes and adds Vec.IsFinite() to check vector coordinates.
Generated from commits between v4.5.6 and HEAD
Bug Fixes
- Added Vec.IsFinite() static method for checking if a vector has finite coordinates (#8390)
- Fix crash when isolating curved arrows with degenerate binding geometry (#8390)
- Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.5
tldraw fixes deep link camera state stuck in React strict mode, restoring canvas pointer interactions.
Generated from commits between v4.5.5 and HEAD
Bug Fixes
Fixed camera state getting stuck at 'moving' when using deep links with React strict mode, which blocked all pointer interactions on the canvas. (#8396)
Original source - Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.4
tldraw fixes shapes disappearing when labeled zero-length arrows overlap.
Generated from commits between v4.5.4 and HEAD
Bug Fixes
Fix all shapes disappearing when a labeled arrow has zero length (e.g. when both endpoints overlap) (#8351)
Original source - Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.3
tldraw fixes SDK dynamic import handling and updates the docs releases index with v4.5.
Generated from commits between v4.5.3 and HEAD
Bug Fixes
- docs: add v4.5 to releases index (#8275) (apps/docs)
- fix(sdk): dynamic import extension cherry pick (#8278) (internal/scripts)
- Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Apr 21, 2026
v4.5.1
tldraw adds v4.5.0 release notes and v4.4.1 patch notes documentation.
Generated from commits between v4.5.1 and HEAD
Bug Fixes
docs(releases): add v4.5.0 release notes and v4.4.1 patch notes (#8263) (apps/docs)
Original source - Apr 21, 2026
- Date parsed from source:Apr 21, 2026
- First seen by Releasebot:Feb 1, 2026
- Modified by Releasebot:Apr 21, 2026
v4.5.10
tldraw compiles commits between v4.5.10 and HEAD into a release update.
Generated from commits between v4.5.10 and HEAD
Original source - Mar 18, 2026
- Date parsed from source:Mar 18, 2026
- First seen by Releasebot:Mar 18, 2026
v4.5.0
tldraw releases updates including click-through on transparent image pixels, configurable embed definitions via EmbedShapeUtil.configure, high-DPI image sizing, Editor.resizeToBounds, and SVG sanitization, plus API changes, performance tweaks, and numerous bug fixes.
What's new
Click-through on transparent image pixels (#7942)
Clicking on transparent areas of PNG, WebP, GIF, and AVIF images now selects shapes behind the image instead of the image itself. This works with cropped, flipped, and circle-cropped images.
This is powered by a new Geometry2d.ignoreHit(point) method that allows geometries to reject successful hit tests.
π Configurable embed definitions (#8034)
Embed definitions are now configured through EmbedShapeUtil.configure() instead of the static setEmbedDefinitions() method, which has been deprecated. The embeds prop on Tldraw is deprecated in favor of this approach.
import { EmbedShapeUtil, DEFAULT_EMBED_DEFINITIONS } from 'tldraw' const shapeUtils = [ EmbedShapeUtil.configure({ embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, myCustomEmbed], }), ]Migration guide
Before:
EmbedShapeUtil.setEmbedDefinitions([...DEFAULT_EMBED_DEFINITIONS, myEmbed])After:
const shapeUtils = [ EmbedShapeUtil.configure({ embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, myEmbed], }), ]The embeds prop on Tldraw still works but is deprecated. Use EmbedShapeUtil.configure() instead.
API changes
π EmbedShapeUtil.setEmbedDefinitions() deprecated. Use EmbedShapeUtil.configure({ embedDefinitions: [...] }) instead. (#8034)
π Tldraw embeds prop is deprecated. Configure embed definitions via EmbedShapeUtil.configure(). (#8034)
Add Geometry2d.ignoreHit(point) for rejecting hit tests on transparent pixels. (#7942)
Add Editor.resizeToBounds(shapes, bounds) for resizing shapes to fit target bounds. (#8120)
Add Editor.getResizeScaleFactor() for computing the dynamic size scale factor at the current zoom level. (#8042)
Add sanitizeSvg(svgText: string) export for sanitizing SVG content against XSS and data exfiltration. (#7896)
Add experimental experimental__onDropOnCanvas option to intercept canvas drop events. Return true from the callback to prevent the editor's default drop behavior. (#7911)
<Tldraw
options={{
experimental__onDropOnCanvas: (point, event) => {
// Handle drop at page-space point
return true // prevent default
},
}}
/>Add optional pixelRatio property to TLImageAsset for correct high-DPI image sizing. Detected automatically from PNG metadata. (#8163)
Replace TypeScript enums (MigrationFailureReason, PORTRAIT_BREAKPOINT) with const object + type alias pattern for compatibility with Node's built-in TypeScript support (strip-types). Values are unchanged. (#8084)
Improvements
- Improve arrow component rendering performance with finer-grained reactivity. (#8167)
- Simplify paste-parent selection to use canReceiveNewChildrenOfType instead of frame-specific checks. Prevent edge-only overlap from auto-reparenting pasted shapes into adjacent frames. (#8057)
- Save link and alt-text values when clicking outside the editor instead of discarding changes. (#8037)
- Add SVG sanitization on paste and file drop to prevent XSS and data exfiltration. (#7896)
- Fix circular dependencies across @tldraw/state, @tldraw/editor, and @tldraw/tldraw packages to improve compatibility with Jest mocking and tree-shaking. (#7935)
- Fix high-DPI image sizing to work correctly across macOS and Windows by detecting the source DPI baseline from PNG metadata. (#8163)
Bug fixes
- Fix shapes pasted with Ctrl+V not being parented to a frame when they land inside one. (#7938)
- Fix a crash when cropping custom shapes that don't include isCircle in their crop schema. (#7931)
- Fix a crash when loading session state without a currentPageId (e.g. when using deep links). (#7994)
- Fix U+2028/U+2029 line separators breaking chunked sync messages. (#7918)
- Fix "Download original" not triggering a download for cross-origin assets. (#8090)
- Fix arrow endpoints terminating at invisible clipped shape boundaries instead of at the frame edge. (#7932)
- Fix sticky notes having a hard shadow instead of a soft drop shadow when exported as SVG. (#7934)
- Fix rich text toolbar staying open when the editing shape is deleted by another user. (#8050)
- Fix SVG sanitizer stripping embedded SVG data URIs on elements. Nested SVGs are now recursively sanitized instead of blocked. (#8087)
- Fix TldrawSelectionForeground crashing when used without TldrawUiContextProvider. (#8011)
- Fix dynamic-size shapes losing shadows and dashes too early when zoomed out. (#8040)
- Fix a crash when resizing draw or highlight shapes to zero width or height. (#8067)
- Fix crash when enabling Debug SVG with shapes on the canvas. (#8101)
- Fix localOffset mutation bug in stretchShapes when shapes have parent transforms. (#8120)
- Fix arrow SVG export producing invalid negative dimensions when arrows have no text label. (#8137)
- Fix arrow endpoint flickering when anchors are at exact shape boundaries. (#8130)
- Fix shapes dragged from the toolbar not respecting dynamic size mode. (#8042)
- Fix dragging unselected shapes that have an onClick handler. (#7936)
- Fix false positive "multiple instances" warning in Next.js dev mode. (#7933)
- Fix missing alt text on rendered image shapes in some cases. (#8158)
- Fix drawing on tablets that report zero pen pressure. (#5693)
- Fix create-tldraw CLI to always create a subdirectory from the project name. (#8161)
- Feb 18, 2026
- Date parsed from source:Feb 18, 2026
- First seen by Releasebot:Feb 19, 2026
v4.4.0
New release adds canvas-based shape indicators, faster rendering, and a telestrator laser with grouped sessions, plus a host of UX and performance upgrades. Highlights include quick zoom, new fill styles, a TldrawUiSelect, consolidated options, and starter templates for image pipelines and agents.
What's new
This release adds a consolidated options prop, quick zoom navigation, a fill styles dropdown, a new TldrawUiSelect component, and shape-aware binding checks. It also includes 2D canvas rendering for shape indicators, R-tree spatial indexing, telestrator-style laser behavior, significant performance improvements for large canvases, and various bug fixes.
- 2D canvas rendering for shape indicators (#7708)
- Shape indicators (selection outlines, hover states) now render using a 2D canvas instead of SVG elements. This significantly improves performance when selecting or hovering over many shapes, with up to 25x faster rendering in some scenarios.
- Custom shapes can opt into canvas indicators by implementing the new getIndicatorPath() method on their ShapeUtil, and marking useLegactIndicator to return false:
- class MyShapeUtil extends ShapeUtil {
getIndicatorPath(shape: MyShape): TLIndicatorPath | undefined {
return {
path: new Path2D(),
// optional clip path for complex shapes like arrows with labels
}
}
}
// Return false to use the new canvas indicators (default is true for backwards compatibility)
useLegacyIndicator(): boolean {
return false
}
}
- class MyShapeUtil extends ShapeUtil {
- Quick zoom navigation (#7801, #7836)
- Press z then hold Shift to zoom out and see the whole canvas ("eagle eye" view). A viewport brush appears showing where you'll zoom to. Move the cursor to pick a location and release Shift to zoom there. Press Escape to cancel and return to the original view.
- Fill styles dropdown (#7885)
- The style panel now exposes additional fill styles (pattern, fill, lined-fill) through a dropdown picker after the solid button. The first three fill options (none, semi, solid) remain as inline buttons.
- User preference: Invert mouse wheel zoom direction (#7732)
- Added a new user preference to invert mouse wheel zoom direction. Some users prefer "natural" scrolling behavior where scrolling down zooms out, which this option now enables.
- Access it via Menu β Preferences β Input device β Invert mouse zoom.
- Performance improvements (#7676, #7826, #7840, #7657)
- This release includes several performance improvements for large canvases:
- R-tree spatial indexing: Shape queries now use an R-tree (RBush) for O(log n) lookups instead of O(n) iteration, significantly improving brushing, scribble selection, and erasing with many shapes on the canvas. The spatial index is maintained internally and accessed through existing methods like editor.getShapesAtPoint() and editor.getShapeAtPoint().
- Faster panning: Hover hit-testing is now skipped during camera movement, reducing work while panning through large documents.
- Reduced allocations: Optimized Set comparisons, reduced memory allocations, and added string hash caching for better performance in large canvases and multiplayer rooms.
- Smarter network scheduling: Solo-mode network traffic is reduced by throttling to 1 FPS when no collaborators are present.
- Re-designed laser pointer (#7681)
- The laser pointer now behaves like a telestrator: all strokes remain visible while you're drawing and fade together when you stop. Previously, each stroke segment would fade independently, creating a trailing effect.
- This is powered by a new generic session system on ScribbleManager. Sessions group multiple scribbles together and control how they fade:
// Start a grouped session (used internally by the laser tool) const sessionId = editor.scribbles.startSession({ fadeMode: 'grouped', idleTimeoutMs: 1200, fadeDurationMs: 500, }) // Add scribbles and points to a session editor.scribbles.addScribbleToSession(sessionId, { color: 'laser' }) editor.scribbles.addPointToSession(sessionId, scribbleId, x, y) // Stop or clear a session editor.scribbles.stopSession(sessionId) editor.scribbles.clearSession(sessionId) - New option in TldrawOptions:
- laserFadeoutMs (default: 500ms) - How long to fade all laser scribbles after the session ends
- Image pipeline starter template (#7863)
- A new "Image pipeline" starter template is available via npx create-tldraw. It provides a visual node-based canvas for building AI image generation workflows, with custom node shapes, typed port connections, pipeline regions with play/stop controls, and a DAG-based execution engine backed by a Cloudflare Worker API.
- Agent starter template improvements (#7640)
- The agent starter template has been restructured around a manager-based architecture for better modularity and extensibility. It now includes a mode system for controlling agent capabilities per mode, action schema registries, prompt part definitions, canvas linting, and user action tracking. The template also renames "SimpleShape" to "FocusedShape" for clarity.
- TldrawUiSelect component (#7566)
- New select dropdown primitive wrapping Radix UI's Select, following existing tldraw UI patterns.
- π Consolidated options prop (#7888)
- The cameraOptions, textOptions, and deepLinks props on Tldraw, TldrawEditor, and TldrawImage are now consolidated into the options prop. The standalone props are deprecated but still work for backward compatibility.
- // Before
- <Tldraw cameraOptions={{ isLocked: true }} deepLinks textOptions={{ ... }} />
- // After
- <Tldraw options={{ camera: { isLocked: true }, deepLinks: true, text: { ... } }} />
- Migration guide
- Replace standalone props with equivalent options fields:
- cameraOptions={...} β options={{ camera: { ... } }}
- textOptions={...} β options={{ text: { ... } }}
- deepLinks or deepLinks={...} β options={{ deepLinks: true }} or options={{ deepLinks: { ... } }}
- The deprecated props still work. When both the deprecated prop and the options field are provided, the options value takes precedence.
API changes
- π TldrawEditorBaseProps.cameraOptions, TldrawEditorBaseProps.textOptions, TldrawEditorBaseProps.deepLinks deprecated in favor of options.camera, options.text, options.deepLinks. (#7888)
- π TLShapeUtilCanBindOpts.fromShapeType and toShapeType replaced with fromShape and toShape accepting TLShape | { type }. (#7821)
- Remove editor.spatialIndex from public API. The spatial index is now internal; use editor.getShapesAtPoint() and editor.getShapeAtPoint() for shape queries. (#7699)
- Add TldrawOptions.camera, TldrawOptions.text, and TldrawOptions.deepLinks fields to the options prop. (#7888)
- Add TldrawUiSelect, TldrawUiSelectTrigger, TldrawUiSelectValue, TldrawUiSelectContent, TldrawUiSelectItem components and associated prop types. Add iconTypes export for enumerating available icons. (#7566)
- Add useCanApplySelectionAction() hook for checking if selection actions should be enabled. (#7811)
- Add TLInstance.cameraState: 'idle' | 'moving' for tracking camera movement state. (#7826)
- Add quickZoomPreservesScreenBounds to TldrawOptions. (#7836)
- Add fillExtra to STYLES object for additional fill style options. (#7885)
- Make Editor.getShapeIdsInsideBounds() public. (#7863)
- Add ShapeUtil.getIndicatorPath() method and TLIndicatorPath type for canvas-based indicator rendering. Add ShapeUtil.useLegacyIndicator() to control whether shapes use SVG or canvas indicators. (#7708)
- Add isZoomDirectionInverted to TLUserPreferences interface and UserPreferencesManager.getIsZoomDirectionInverted() method. Add ToggleInvertZoomItem component export and toggle-invert-zoom action. (#7732)
- Add complete to TL_SCRIBBLE_STATES enum and ScribbleManager.complete(id) method for marking scribbles as complete before fading. (#7760)
- Add scribble session system: ScribbleManager.startSession(), stopSession(), clearSession(), extendSession(), isSessionActive(), addScribbleToSession(), addPointToSession(), and ScribbleSessionOptions type. Add LaserTool.getSessionId(). Add laserFadeoutMs to TldrawOptions. (#7681)
- Add FpsScheduler class to create FPS-throttled function queues with configurable target rates. (#7418)
Improvements
- Improve performance in large canvases and multiplayer rooms by optimizing Set comparisons, reducing memory allocations, and caching string hashes. (#7840)
- Improve panning performance in large documents by skipping hover hit-testing during camera movement. (#7826)
- Improve performance when translating arrows together with bound shapes by skipping unnecessary reparenting. (#7733)
- Add R-tree spatial indexing for O(log n) shape queries, improving performance of brushing, selection, and erasing with many shapes. (#7676)
- Improve laser pointer to keep all strokes visible during a session (telestrator pattern), with strokes fading together when drawing stops. (#7681)
- Improve laser pointer strokes with proper taper when lifting the pointer by adding a 'complete' state to the scribble lifecycle. (#7760)
- Improve quick zoom to use zoom-to-fit instead of fixed 5%, add "quick peek" behavior, and add edge scrolling. (#7836)
- Remove core-js polyfill dependency from @tldraw/editor. (#7769)
- Reduce network traffic by squashing pending push requests before sending, so rapid edits result in fewer network calls. (#7724)
- Reduce solo-mode network traffic by using a dedicated FPS-based scheduler that throttles to 1 FPS when no collaborators are present. (#7657)
- Improve performance by separating UI and network scheduling into independent queues with configurable target FPS. (#7418)
- Improve frame label sizing on smaller viewports and when zoomed out. (#7746)
- Add image pipeline starter template with visual node-based AI image generation workflows. (#7863)
- Restructure agent starter template with manager-based architecture and mode system. (#7640)
Bug fixes
- Fix geo shapes with text not being resizable to a smaller size. (#7878)
- Fix shapes exploding when resizing unaligned arrows. (#7855)
- Fix Safari pinch zoom resetting selection to previous shapes. (#7777)
- Fix toggle-lock action firing when no shapes are selected. (#7815)
- Fix menu items (zoom to selection, cut, copy, delete) being enabled when not in select tool. (#7811)
- Fix excessive tab indentation in text shapes (now 2 spaces instead of 8). (#7796)
- Fix canvas-in-front elements (cursors, following indicators) appearing in front of UI panels instead of behind them. (#7865)
- Fix license console message colors for warnings and errors. (#7850)
- Fix asset resolution being delayed by 500ms when updating an image shape's asset. (#7612)
- Fix Durable Object SQLite migration in the multiplayer Cloudflare template. (#7829, #7832, #7834, #7835)
- Fix zoomToFit and getCurrentPageBounds to ignore hidden shapes when computing bounds. (#7770)
- Fix collaborator shape indicators not rendering after the canvas indicator change. (#7759)
- Fix rich text content not updating correctly with message squashing due to reference comparison. (#7758)
- Fix keyboard shortcut menu item labels to use consistent ellipsis formatting. (#7757)
- Fix spatial index not removing shapes when moved to a different page. (#7700)
- Fix tldraw failing to load in CJS environments (tsx, ts-node, Jest) due to ESM-only rbush dependency. (#7905)
- Feb 11, 2026
- Date parsed from source:Feb 11, 2026
- First seen by Releasebot:Feb 11, 2026
v3.15.6
TLDraw releases bring a license log color fix to clearly separate warnings from errors, plus editor and core fixes. It tightens license messaging, fixes expired license logic, race conditions, and UI tweaks while boosting docs and asset handling for a smoother workflow.
Release Notes
Note
Low Risk
Cosmetic logging-only changes in license diagnostics; no impact on license validation, state, or data handling.
Overview
Fixes LicenseManager verbose console output so warning vs error messages are visually distinct and consistent.
outputDelimiter now takes the message type to match delimiter background color, and message text styling is standardized to white text with an orange/crimson background instead of varying foreground colors.
Written by Cursor Bugbot for commit faf3005. This will update automatically on new commits. Configure here.
Describe what your pull request does. If you can, add GIFs or images showing the before and after of your change.improvement(editor): make license flags message a warning (#6843)
license: make the flags msg a warning
docs: improve code snippets, safe areas, and case study images (#6697)
Improved documentation site with copy buttons, optimized images, and layout fixes.
fix(editor): resolve expired internal license logic (#6665)
Fix a bug with expired internal license check.
docs: update license page language (#6668)
Updated the license page with current language.
fix(sync-core): resolve potential race condition in TLSyncClient (#6652)
Fixed a potential issue in TLSyncClient.
feat(editor): add missing shapeutil callbacks (#6634)
Add missing shapeutil callbacks.
fix(ui): memoize asset urls merging (#6605)
Fixed a bug where the editor would remount in a loop if given custom assetUrls.
fix(analytics): remove redundant initial page view tracking (#6601)
Improved analytics tracking by removing redundant page view events
docs: clarify third-party backend documentation (#6570)
Clarified documentation regarding third-party backends.
fix(sync-core): room change listeners (#6552)
Fixed an issue with room change listeners in sync-core
π Bug Fix
docs: update license page language #6668 (@sylwiavargas)
docs: clarify third-party backend documentation #6570 (@sylwiavargas @steveruizok)
@tldraw/editor
license: fix console message colors for warnings/errors (#7850) #7884 (@mimecuvalo)
docs: improve code snippets, safe areas, and case study images #6697 (@SomeHats)
β οΈ Pushed to v3.15.x
@tldraw/ai, @tldraw/assets, create-tldraw, @tldraw/editor, @tldraw/tldraw, @tldraw/state-react, @tldraw/state, @tldraw/store, @tldraw/sync-core, @tldraw/sync, tldraw, @tldraw/tlschema, @tldraw/utils, @tldraw/validate
v3.15.0 (@huppy-bot[bot])
π Bug Fixes
fix(analytics): remove redundant initial page view tracking #6601 (@MitjaBezensek)
@tldraw/editor
fix(editor): resolve expired internal license logic #6665 (@MitjaBezensek)
@tldraw/sync-core
fix(sync-core): resolve potential race condition in TLSyncClient #6652 (@ds300)
fix(sync-core): room change listeners #6552 (@mootari @ds300)
tldraw
fix(ui): memoize asset urls merging #6605 (@ds300)π Product Improvements
@tldraw/editor
improvement(editor): make license flags message a warning #6843 (@mimecuvalo)π New Features
@tldraw/editor, tldraw
feat(editor): add missing shapeutil callbacks #6634 (@SomeHats @ds300)Authors: 8
@huppy-bot[bot]
Original source
alex (@SomeHats)
David Sheldrick (@ds300)
Fabian Iwand (@mootari)
Mime Δuvalo (@mimecuvalo)
Mitja BezenΕ‘ek (@MitjaBezensek)
Steve Ruiz (@steveruizok)
Sylwia Vargas (@sylwiavargas) - Jan 21, 2026
- Date parsed from source:Jan 21, 2026
- First seen by Releasebot:Jan 21, 2026
v4.3.0
New release introduces pluggable TLSocketRoom storage with SQLite, reactive editor.inputs, and delta-encoded draw data for massive storage savings. It adds TypeScript module augmentation for custom shapes, React 19 upgrade, and broad API tweaks with bug fixes for stability.
This release
This release introduces several significant changes: a new pattern for defining custom shape/binding typings, pluggable storage for TLSocketRoom with a new SQLite option, reactive editor.inputs, and optimized draw shape encoding. It also adds various other API improvements, performance optimizations, and bug fixes, including better support for React 19.
New pattern for defining custom shape/binding types (breaking change) (#7091)
We've improved the developer experience of working with custom shape and binding types. There's now less boilerplate and fewer gotchas when using tldraw APIs in a type-safe manner.
This is a minor breaking change at the type levelβyour code will still run, but you'll get TypeScript errors until you migrate.
Migration guide
When declaring types for custom shapes, you can now use TypeScript's module augmentation feature to provide more specific types for the custom shape.
Before:
import { TLBaseShape } from 'tldraw' // Shapes were defined by using the helper TLBaseShape type type MyShape = TLBaseShape<'my-shape', { w: number; h: number; text: string }>After:
import { TLShape } from 'tldraw' const MY_SHAPE = 'my-shape' // We now use TypeScript's module augmentation feature to allow // extending the builtin TLShape type. declare module 'tldraw' { export interface TLGlobalShapePropsMap { [MY_SHAPE]: { w: number; h: number; text: string } } } type MyShape = TLShape<typeof MY_SHAPE>The benefit of this new system is that Editor APIs such as createShape now know about your custom shapes automatically:
// Just works - TypeScript validates props and provides autocomplete
editor.createShape({ type: 'my-shape', props: { w: 100, h: 100, text: 'Hello' } })
// Will cause a TypeScript error fortext
editor.createShape({ type: 'my-shape', props: { w: 100, h: 100, text: 123 } })The same pattern applies to custom bindings. See the Custom Shapes Guide and the Pin Bindings example for details.
(contributed by @Andarist)Pluggable storage for TLSocketRoom + SQLite support (#7320, #7123)
We've refactored the TLSocketRoom API to support a pluggable storage layer. We're providing two implementations:
- SQLiteSyncStorage β Automatically persists room state to SQLite. Recommended for production.
- InMemorySyncStorage β Keeps state in memory with manual persistence via callbacks (previous built-in behavior).
We recommend switching to SQLiteSyncStorage if your environment supports SQLite (Cloudflare Durable Objects, Node.js, Bun, Deno). It provides automatic persistence, lower memory usage, and faster startup times.
Why SQLite?
- Automatic persistence: Data survives process restarts without manual snapshot handling
- Lower memory usage: No need to keep entire documents in memory
- Faster startup: No need to load the document into memory before accepting socket connections
- Simpler code: No more onChange callbacks and manual persistence logic
Platform support
- Platform
- Wrapper
- SQLite Library
- Cloudflare Durable Objects
- DurableObjectSqliteSyncWrapper
- Built-in ctx.storage
- Node.js/Deno
- NodeSqliteWrapper
- better-sqlite3 or node:sqlite
See the Cloudflare template and the Node server example respectively. Bun support should be straightforward to add.
Migration guide
Existing code continues to work, however we have deprecated the following TLSocketRoom options:
initialSnapshot
onDataChangeThese are replaced by the new storage option. We've also deprecated the TLSocketRoom.updateStore method, which has been supplanted by storage.transaction.
Before:
const existingSnapshot = loadExistingSnapshot() const room = new TLSocketRoom({ initialSnapshot: existingSnapshot, onDataChange: () => { persistSnapshot(room.getCurrentSnapshot()) }, })If you want to keep the same behavior with in-memory document storage and manual persistence:
import { InMemorySyncStorage, TLSocketRoom } from '@tldraw/sync-core' const room = new TLSocketRoom({ storage: new InMemorySyncStorage({ snapshot: existingSnapshot, onChange() { saveToDatabase(storage.getSnapshot()) }, }), })However, we recommend switching to SQLite. Users of our Cloudflare template should follow the migration guide on the sync docs page.
If you're using TLSocketRoom on Node, creating the room should end up looking something like this:
import Database from 'better-sqlite3' import { SQLiteSyncStorage, NodeSqliteWrapper, TLSocketRoom, RoomSnapshot } from '@tldraw/sync-core' async function createRoom(roomId: string) { const db = new Database(`path/to/${roomId}.db`) const sql = new NodeSqliteWrapper(db) let snapshot: RoomSnapshot | undefined = undefined if (!SQLiteSyncStorage.hasBeenInitialized(sql)) { // This db hasn't been used before, so if it's a pre-existing // document, load the legacy room snapshot snapshot = await loadExistingSnapshot() } const storage = new SQLiteSyncStorage({ sql, snapshot }) return new TLSocketRoom({ storage, onSessionRemoved(room, args) { if (args.numSessionsRemaining === 0) { room.close() db.close() } }, }) }Optimized draw shape encoding (#7364, #7710)
Draw and highlight shape point data is now stored using a compact delta-encoded binary format instead of JSON arrays. This reduces storage size by approximately 80% while preserving stroke fidelity.
Breaking change details
If you were reading or writing draw shape data programatically you might need to update your code to use the new format.
- TLDrawShapeSegment.points renamed to .path and changed from VecModel[] to string (base64-encoded)
- Added scaleX and scaleY properties to draw and highlight shapes
- New exports: b64Vecs encoding utilities, e.g. getPointsFromDrawSegment helper. Use this if you need to manually read/write point data.
- Existing documents are automatically migrated.
Reactive inputs (#7312)
Refactored editor.inputs to use reactive atoms via the new InputsManager class. All input state is now accessed via getter methods (e.g., editor.inputs.getCurrentPagePoint(), editor.inputs.getShiftKey()). Direct property access is deprecated but still supported for backwards compatibility.
API changes
- π₯ DefaultTopPanel export removed from tldraw. The top panel component for displaying the offline indicator is now handled internally by PeopleMenu. (#7568)
- π₯ TextDirection export removed from tldraw. Use TipTap's native TextDirection extension instead. The richTextValidator now includes an optional attrs property - a migration may be necessary for older clients/custom shapes. (#7304)
- Add tlenvReactive atom to @tldraw/editor for reactive environment state tracking, including coarse pointer detection that updates when users switch between mouse and touch input. (#7296)
- Add hideAllTooltips() helper function for programmatically dismissing tooltips. (#7288)
- Add zoomToFitPadding option to TldrawOptions to customize the default padding used by zoom-to-fit operations. (#7602)
- Add snapThreshold option to TldrawOptions for configuring the snap distance, defaulting to 8 screen pixels. (#7543)
- Add resizeChildren configuration option to FrameShapeUtil to allow frame children to be resized proportionally when the frame is resized. (#7526)
- Add Editor.canEditShape() and Editor.canCropShape() methods to centralize shape permission checks. Add ShapeUtil.canEditWhileLocked() for shapes that remain editable when locked. (#7361)
- Add editor.getDebouncedZoomLevel() and editor.getEfficientZoomLevel() methods for improved zoom performance on dense canvases. Add debouncedZoom and debouncedZoomThreshold options. (#7235)
- Add configurable showTextOutline option to TextShapeUtil, ArrowShapeUtil, and GeoShapeUtil via .configure() pattern. (#7314)
- Export freehand stroke utilities: getStroke, getStrokeOutlinePoints, and setStrokePointRadii. (#7400) (contributed by @VimHax)
- Add Box.isValid() method to check for finite coordinates. (#7532)
- Add spacebarPanning option to control whether spacebar activates pan mode. (#7312)
- Introduce pluggable TLSyncStorage API for TLSocketRoom. The initialSnapshot and onDataChange options are now deprecated in favor of the new storage option. (#7123)
- Export DefaultLabelColorStyle which is necessary for rich text in custom shapes. (#7114)
Improvements
- Improve coarse pointer detection by replacing CSS media queries with a reactive data-coarse attribute that updates when users switch between mouse and touch input. (#7404)
- Add CSS containment to main toolbar and text measurement element for improved rendering performance. (#7406) (#7407)
- Improve cross-realm support by scoping canvas event listeners to the editor container's ownerDocument. (#7113)
- Simplify ImmutableMap implementation for better code clarity. (#7431)
- Improve code readability in number validator by using Number.isFinite() instead of arithmetic trick. (#7374)
- Upgrade to React 19 with all necessary type and configuration changes for compatibility. (#7317)
- Improve signal graph traversal performance by eliminating per-recursion closure allocations. (#7430)
- Optimize object utility functions. (#7432)
- Use in-place sorting in parentsToChildren derivation. (#7433)
- Optimize notVisibleShapes derivation with inlined bounds checks. (#7429)
- Cache label size measurements with WeakCache for improved geo shape performance. (#7412)
- Optimize validators with fast paths and production inlining. (#7373)
Bug fixes
- Fix migrations for draw and highlight shapes to be idempotent, preventing errors when migrations run multiple times. (#7389)
- Fix dot detection in draw and highlight shapes after the point compression change. (#7365)
- Fix clicking a shape's text label while editing to re-focus the input and select all text. (#7342)
- Fix pasting at cursor to correctly account for frames and parent containers. (#7277)
- Fix editing mode to exit when dragging causes the text input to blur. (#7291)
- Fix CommonJS build issues with TipTap imports in rich text module. (#7282)
- Fix iOS automatically zooming in on input fields by ensuring 16px minimum font size. (#7118)
- Fix distanceToLineSegment returning squared distance instead of actual distance, causing hit testing (eraser, scribble select) to be too strict. (#7610) (contributed by @arpit-goblins)
- Fix zoomToSelection to toggle between 100% zoom and zoom-to-fit behavior. (#7536)
- Fix export of SVG markers by handling fragment-only URLs correctly. (#7506) (contributed by @PidgeyBE)
- Restore wheel and pinch canvas event emission. (#6834) (contributed by @swdev33)
- Fix context menu submenu flickering when hovered. (#6837) (contributed by @swdev33)
- Fix rotated shape positions not being restored after flipping twice. (#7359)
- Fix keyboard shortcuts and clipboard events not working when hideUi is true. (#7367)
- Fix elbow arrows routing incorrectly when using dynamic sizing at high zoom levels. (#7424)
- Allow fullscreen for embed shapes. (#7417)
- Move mobile rotate handle to bottom for image and video shapes to accommodate the contextual toolbar. (#6727)
- Fix dropdown menu items incorrectly displaying an icon on the right side. (#7533)
- Fix zoom menu showing debounced zoom level for canvases with many shapes. (#7626)
- Fix extra line appearing after bullet and ordered lists in text shapes. (#7643) (contributed by @sahiee-dev)
- Fix "Back to content" button not appearing when selected shapes are off-screen. (#7649)
- Fix dotted freehand lines becoming invisible at minimum zoom on Chrome. (#7650)
- Fix menu bar stretching to full width on mobile viewports instead of fitting content. (#7568)
- Jan 8, 2026
- Date parsed from source:Jan 8, 2026
- First seen by Releasebot:Jan 9, 2026
v4.2.2 (deprecated)
β οΈ DO NOT USE THIS RELEASE β οΈ
Due to an issue with internal tooling we mistakenly released 4.2.2 from our in-progress 4.3 branch. Use 4.2.3+ instead
Original source
Curated by the Releasebot team
Releasebot is an aggregator of official release notes from hundreds of software vendors and thousands of sources.
Our editorial process involves the manual review and audit of release notes procured with the help of automated systems.
Similar to tldraw with recent updates:
- Obsidian release notes84 release notes Β· Latest Mar 23, 2026
- Tailscale release notes84 release notes Β· Latest Apr 29, 2026
- Ubiquiti release notes570 release notes Β· Latest May 12, 2026
- Perplexity release notes24 release notes Β· Latest May 11, 2026
- Raycast release notes10 release notes Β· Latest Dec 16, 2025
- Notion release notes125 release notes Β· Latest May 11, 2026