tldraw, infinite canvas and the SDK licence
tldraw is a React library giving you a working infinite canvas editor: shapes, arrows bound to objects, layers, undo, export, and sync between users. You embed one component and have a working board.
Before you write it into a project, though, read the next section. The terms of use changed in a way that caught many people off guard, and most material online still describes the state before that change.
The licence, the thing to know first
Through version 3, tldraw could be used with no formalities. Since version 4.0, released in September 2025, the SDK does not run in production without a valid licence key, and the source sits under the authors' own licence rather than Apache 2.0 or MIT. The current series is version five, released in May 2026, and that is what the documentation and the examples describe.
The three paths look like this.
The trial runs a hundred days and is free, granted after filling in a form. A trial key gets no transition period, unlike an annual licence, which keeps working normally for thirty days past its expiry date. Worth knowing how that looks in practice: the editor starts normally, runs for about five seconds, then removes itself from the page and leaves an empty element behind. To a user that reads as an application crash rather than a licence notice.
The hobby licence covers non-commercial projects and requires showing a "made with tldraw" watermark on the canvas. It is granted at the team's discretion, after you describe the project.
The commercial licence requires contacting the sales team. Pricing is not published, and the figure the community quotes publicly, on the order of six thousand dollars a year per team, is a reference point rather than an offer. Startups sometimes get separate terms, so if you are building a product, just ask.
The key is validated on the browser side, through signature verification, so validation itself needs no network. Usage reporting is a separate matter. On a trial key, on a hobby licence carrying the watermark, and on a production deployment without a valid key, the library calls the vendor with the SDK version, the licence type and id, and the full address of the page it started on. It never sends canvas contents or user data, but it does send the deployment address. On an ordinary commercial licence with no watermark that request is not made at all. The key is also tied to a list of domains, so the same string will not work under a different address.
The practical conclusion: tldraw stopped being a candidate for a board in an unfunded side project. For play and for learning it still fits; for a commercial product it requires a financial decision taken deliberately rather than discovered on deployment day.
When it is the right choice
Since it costs money, the question becomes what the money buys.
It buys the fact that an infinite canvas is far harder to write than it looks. Arrows alone, the kind that stay attached to shapes through moving, rotating, and resizing, are weeks of work. Add collision detection, snapping, multi-select, grouping, an edit history resilient to concurrent changes, and stylus support with pressure.
tldraw has all of that polished to a level where the seams do not show. If you are building a product where the canvas is the main interface, a process design tool, a space planner, a diagram editor, a hundred day trial is plenty to judge whether writing your own would cost more than a year of licence. It usually would.
If instead you need simple drawing beside the application's main function, annotations on an image or a small sketchpad in notes, this is a cannon aimed at a sparrow and something lighter fits better.
First run
pnpm add tldrawimport { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export function Whiteboard() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw licenseKey={process.env.NEXT_PUBLIC_TLDRAW_LICENSE} />
</div>
)
}Two things cause the most trouble on a first attempt.
The stylesheet has to be imported manually. Without it the component renders but looks like a scattered pile of unpositioned elements, easily mistaken for a build configuration fault.
The container needs a defined height. The canvas stretches to its parent, so inside an auto height element it gets zero pixels and vanishes. Set height in pixels, in percent, or use fixed positioning.
In Next.js a third thing appears: the library touches browser objects on mount, so server rendering has to be disabled.
import dynamic from 'next/dynamic'
const Tldraw = dynamic(
async () => (await import('tldraw')).Tldraw,
{ ssr: false }
)Persisting state
The canvas does not survive a page reload by default. You add persistence yourself, and that is the first architectural decision to take.
import { Tldraw, createTLStore, defaultShapeUtils, getSnapshot, loadSnapshot } from 'tldraw'
import { useEffect, useState } from 'react'
const KEY = 'board'
export function PersistentBoard() {
const [store] = useState(() => {
const store = createTLStore({ shapeUtils: defaultShapeUtils })
const saved = localStorage.getItem(KEY)
if (saved) loadSnapshot(store, JSON.parse(saved))
return store
})
useEffect(() => {
return store.listen(() => {
localStorage.setItem(KEY, JSON.stringify(getSnapshot(store)))
})
}, [store])
return <Tldraw store={store} />
}Note the shape of the snapshot calls, since that is one of the most common places where an old example from the web stops compiling. getSnapshot and loadSnapshot are functions taking the store as their first argument rather than methods on it. Writing store.getSnapshot() or store.loadSnapshot(data) comes from older versions, and the store class no longer carries those methods.
That code works and suffices for a prototype, but carries a flaw visible only on a larger drawing. The listener fires on every change, including every frame of dragging a shape, so you write the whole document dozens of times per second. At two hundred shapes that stutters noticeably.
The fix is simple: delay the write a few hundred milliseconds past the last change. Writing to a server, the delay should be larger, around two seconds, and sending differences beats sending the full state.
Custom shapes
This is the main reason to reach for tldraw rather than a finished board. You can add your own object type that behaves like the built in shapes: selectable, rotatable, a valid arrow target, and undoable on creation.
import { BaseBoxShapeUtil, HTMLContainer, T, TLBaseShape } from 'tldraw'
type CardShape = TLBaseShape<'card', { w: number; h: number; title: string }>
export class CardUtil extends BaseBoxShapeUtil<CardShape> {
static override type = 'card' as const
static override props = { w: T.number, h: T.number, title: T.string }
override getDefaultProps(): CardShape['props'] {
return { w: 220, h: 120, title: 'New card' }
}
override component(shape: CardShape) {
return (
<HTMLContainer style={{ padding: 12, background: '#fff', border: '1px solid #ddd' }}>
<strong>{shape.props.title}</strong>
</HTMLContainer>
)
}
override getIndicatorPath(shape: CardShape) {
const path = new Path2D()
path.rect(0, 0, shape.props.w, shape.props.h)
return path
}
}The component method returns ordinary React, so inside a shape you can place any interface: a text field, a chart, an image pulled from a server. The getIndicatorPath method draws the outline shown on selection and returns a path painted onto the canvas. Through version four the same role belonged to the indicator method, which returned an SVG element. Version five moved outlines onto the canvas layer, and the old method stayed in the code purely so older classes keep type checking; it draws nothing. Examples copied from material predating that change therefore produce a shape with no visible selection.
Two pitfalls. Shape properties go through validation on every change, so altering the schema in a live application requires a migration, otherwise old documents stop loading. And: a heavy component inside a shape re renders on every move, so across hundreds of objects it deserves memoisation.
Realtime collaboration
Sync is a separate package and a separate decision. The demo server suits a prototype but not production, since the data sits with somebody else and can disappear.
A widespread misunderstanding deserves correcting here. There is no hosted sync service sold separately. The documentation states plainly that beyond a prototype you host the server yourself, and the sync library itself falls inside the SDK licence rather than appearing as another line on the bill.
So there are two routes and both run through your own infrastructure. The first is the ready Cloudflare template, the same arrangement that runs tldraw.com: a separate durable object per room and object storage for images and video. The second is wiring the sync core package into your own JavaScript backend that supports websockets.
The choice comes down to how much you want to maintain. The template shortens the path to a working room, while authentication, upload size limits, document history, and room search remain yours to add. Either way the cost has to include several days of work plus maintenance.
Permissions also deserve thought sooner rather than later. Sync itself does not know who may edit and who may only watch, so access control is on you. Read only mode gets set on the client, but a client can be tricked, so the server has to reject changes from anyone without rights.
tldraw against the alternatives
| Option | Licence | Production cost | Pick it when |
|---|---|---|---|
| tldraw | Own licence, key required | Paid annual licence | The canvas is the heart of the product |
| Excalidraw | MIT | Zero | Hand drawn sketches, zero budget |
| Konva or Fabric | MIT | Zero | You have time to write editor logic |
| A hosted whiteboard | Closed | Per user | You are using a product, not building one |
The second row is today's most common alternative and deserves honest consideration. Excalidraw carries a hand drawn style some people do not want, and thinner extension options, but the MIT licence removes the entire problem described at the top. For simpler requirements that is the rational pick.
The third row means libraries that draw on a canvas without editor logic. You get shapes, transforms, and events, while selection, undo, arrows, and collaboration are yours to write. Sensible when you need something very unusual, senseless when you need an ordinary board.
Staying on version 3 and what that means
Since the key applies from version 4.0 onward, an obvious idea presents itself: stay on three and pay nothing. It deserves consideration, with the consequences in view, and the first of them is distance. Version three shipped in September 2024, which puts two generations of releases and two sets of breaking changes between it and the current version five.
Version 3 keeps its existing terms, so code already running on it keeps running. It receives no fixes, no new capability, and no support. For a library rendering an interface in a browser that is not neutral, since browsers change every few weeks, and pointer event behaviour, stylus handling, and text rendering are a recurring source of new faults that nobody will now repair.
The second consequence concerns dependencies. A frozen version drags along a frozen range of React versions and build tooling. Two years out, updating the rest of the project can force updating this library, and you land back at the same decision with a bigger debt to clear.
The third is people. Material, examples, and answers online increasingly describe the newer version, so a developer joining the project meets documentation that does not match the code. That cost spreads over time and is hard to spot on a spending report.
A sensible route looks like this. If the project is closed, finished, and has a defined lifespan, staying on the older version is rational. If it is meant to grow for years, count the annual licence as a maintenance cost rather than a one off expense, and compare it against the time the team would spend working around problems in a frozen library.
There is a third variant, most rarely considered: leaving tldraw during a rewrite you were doing anyway. The document format is textual and readable, so moving shapes into another library is feasible, though not free. Test that on a sample before the decision gets made the other way under deadline pressure.
Export and performance
Image export works on a selection or on the whole page and takes a scale, so print material comes from raising the multiplier.
const { blob, width, height } = await editor.toImage([...editor.getCurrentPageShapeIds()], {
format: 'png',
scale: 2,
background: true,
})The method returns an object with blob, width, and height fields rather than the binary object alone, so assigning the whole result to a variable named blob produces a file that cannot be written. The vector format keeps its sharpness at any zoom and is the better pick for documentation. A state snapshot in text form is not an image but a full document that can be loaded back, so keep both for archiving.
On performance three things make the difference. The number of shapes visible at once, since anything outside the viewport is not drawn anyway, so a dense cluster of objects in one spot hurts more than a thousand scattered ones. The complexity of custom components, covered above. And full resolution images placed as backgrounds, which can eat memory faster than everything else combined.
Common mistakes
The first is assuming the library is free. Material predating version 4.0, and most of what is online does, talks about an Apache 2.0 licence. That is no longer true, and discovering it after building a product is expensive.
The second is not disabling server rendering. In React running on a server the library breaks on mount, reaching for a browser object.
The third is a container with no height. The canvas then gets zero pixels and looks as though the component never mounted.
The fourth is writing state on every change with no delay. Dragging a shape then produces dozens of writes per second and visible stutter.
The fifth is changing custom shape properties without a migration. Old documents stop loading, and the user sees a blank board instead of their work.
The sixth is relying on the demo server inside a product. It exists for experiments and offers no guarantee that data survives.
The seventh is committing the licence key to a public repository. The key is validated on the browser side, so it reaches the built bundle regardless, but keeping it in source makes swapping it harder and blurs environments. Pull it into an environment variable and set it separately for staging and production.
The eighth is skipping a test on a touch device. The canvas handles touch and stylus, but interface element sizes chosen for a mouse pointer can be unhittable on a phone, and page scrolling competes with panning the view. That surfaces only on real hardware, not in the browser's responsive mode.
FAQ
Is tldraw free?
For local development yes, in production no. Since version 4.0 the SDK requires a licence key: free for a hundred day trial, hobby with a watermark for non-commercial projects, or commercial after contacting the sales team.
What does a commercial licence cost?
Pricing is not public, and the figure circulating in the community, around six thousand dollars a year per team, does not come from an official source. Startups sometimes get separate terms, so a real answer comes only from the sales team.
Does the licence key need a network connection?
No, validation checks a signature in the browser and needs no network. Usage reporting runs separately: on a trial key, on a hobby licence with the watermark, and in production without a valid key, the library sends the vendor the SDK version, the licence type and id, and the page address. It never sends canvas contents or user data.
How does tldraw differ from Excalidraw?
In licence and in purpose. Excalidraw is MIT licensed and aims at hand drawn sketching; tldraw is a paid SDK for building your own canvas editors, with an extensive system of custom shapes and tools. For a simple board the capability gap does not justify the cost gap.
Can I add custom shapes and tools?
Yes, and that is the main reason to reach for this library. A custom shape is a class with methods drawing its contents and its outline, and inside you can place any React component, a form or a chart included.
Documentation and licence terms sit on the developer site, and the code in the GitHub repository.