Documentation Index Fetch the complete documentation index at: https://mintlify.com/tldraw/tldraw/llms.txt
Use this file to discover all available pages before exploring further.
Tldraw provides built-in persistence capabilities to save and restore the editor state. You can use IndexedDB for local storage or implement custom persistence.
Built-in persistence
Use the persistenceKey prop to enable automatic local storage:
PersistenceKeyExample.tsx
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function PersistenceKeyExample () {
return (
< div className = "tldraw__editor" >
< Tldraw persistenceKey = "example" />
</ div >
)
}
With this simple setup:
Editor state is automatically saved to IndexedDB
State is restored when the component mounts
Changes are persisted in real-time
Each persistenceKey creates a separate saved document
The persistenceKey should be unique for each document. If you have multiple editors, use different keys for each one.
How it works
When you set a persistenceKey:
On mount, tldraw loads the saved state from IndexedDB
As users make changes, the state is automatically saved
On next visit, the editor restores to the last saved state
Multiple documents
You can manage multiple documents by using different persistence keys:
import { Tldraw } from 'tldraw'
import { useState } from 'react'
const documents = [
{ id: 'doc-1' , name: 'Design mockups' },
{ id: 'doc-2' , name: 'Flowchart' },
{ id: 'doc-3' , name: 'Whiteboard' },
]
export default function MultiDocExample () {
const [ currentDoc , setCurrentDoc ] = useState ( 'doc-1' )
return (
< div >
< div style = { { padding: 10 } } >
{ documents . map ( doc => (
< button
key = { doc . id }
onClick = { () => setCurrentDoc ( doc . id ) }
style = { {
fontWeight: currentDoc === doc . id ? 'bold' : 'normal' ,
} }
>
{ doc . name }
</ button >
)) }
</ div >
< div className = "tldraw__editor" >
< Tldraw
key = { currentDoc }
persistenceKey = { currentDoc }
/>
</ div >
</ div >
)
}
Use the key prop to force React to remount the component when switching documents. This ensures the correct state is loaded.
Custom persistence
For more control, implement custom persistence using snapshots:
import { Tldraw , TLStoreSnapshot , createTLStore , defaultShapeUtils } from 'tldraw'
import { useEffect , useState } from 'react'
import 'tldraw/tldraw.css'
export default function CustomPersistenceExample () {
const [ store ] = useState (() => createTLStore ({ shapeUtils: defaultShapeUtils }))
const [ isLoaded , setIsLoaded ] = useState ( false )
useEffect (() => {
// Load snapshot from your backend
async function loadSnapshot () {
const response = await fetch ( '/api/load-document' )
const snapshot : TLStoreSnapshot = await response . json ()
store . loadSnapshot ( snapshot )
setIsLoaded ( true )
}
loadSnapshot ()
}, [ store ])
useEffect (() => {
if ( ! isLoaded ) return
// Save snapshot when changes occur
const saveSnapshot = () => {
const snapshot = store . getSnapshot ()
fetch ( '/api/save-document' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( snapshot ),
})
}
// Listen for changes
const dispose = store . listen ( saveSnapshot , { scope: 'document' })
return dispose
}, [ store , isLoaded ])
if ( ! isLoaded ) {
return < div > Loading... </ div >
}
return (
< div className = "tldraw__editor" >
< Tldraw store = { store } />
</ div >
)
}
Snapshots API
Get snapshot
Capture the current editor state:
import { Editor } from 'tldraw'
function saveDocument ( editor : Editor ) {
const snapshot = editor . store . getSnapshot ()
// Save snapshot to your backend
localStorage . setItem ( 'my-document' , JSON . stringify ( snapshot ))
}
Load snapshot
Restore editor state from a snapshot:
import { Editor , TLStoreSnapshot } from 'tldraw'
function loadDocument ( editor : Editor ) {
const data = localStorage . getItem ( 'my-document' )
if ( data ) {
const snapshot : TLStoreSnapshot = JSON . parse ( data )
editor . store . loadSnapshot ( snapshot )
}
}
Partial snapshots
You can also work with partial snapshots to sync only specific changes:
// Get changes since a specific point
const changes = editor . store . extractingChanges (() => {
// Make some changes
editor . createShape ({ type: 'geo' , x: 0 , y: 0 })
})
// Apply changes to another store
otherEditor . store . applyDiff ( changes )
Export and import
Export as JSON
import { Editor } from 'tldraw'
function exportToJSON ( editor : Editor ) {
const snapshot = editor . store . getSnapshot ()
const json = JSON . stringify ( snapshot , null , 2 )
// Download as file
const blob = new Blob ([ json ], { type: 'application/json' })
const url = URL . createObjectURL ( blob )
const a = document . createElement ( 'a' )
a . href = url
a . download = 'drawing.tldr'
a . click ()
}
Import from JSON
import { Editor , TLStoreSnapshot } from 'tldraw'
function importFromJSON ( editor : Editor , file : File ) {
const reader = new FileReader ()
reader . onload = ( e ) => {
const snapshot : TLStoreSnapshot = JSON . parse ( e . target ?. result as string )
editor . store . loadSnapshot ( snapshot )
}
reader . readAsText ( file )
}
Database storage
For server-side persistence:
import { TLStoreSnapshot } from 'tldraw'
// Save to database
async function saveToDatabase ( documentId : string , snapshot : TLStoreSnapshot ) {
await db . documents . update ({
where: { id: documentId },
data: {
content: snapshot ,
updatedAt: new Date (),
},
})
}
// Load from database
async function loadFromDatabase ( documentId : string ) : Promise < TLStoreSnapshot > {
const document = await db . documents . findUnique ({
where: { id: documentId },
})
return document . content
}
Versioning and migrations
Tldraw includes a migration system for handling schema changes:
import { createTLStore , defaultShapeUtils , TLStoreSnapshot } from 'tldraw'
const store = createTLStore ({ shapeUtils: defaultShapeUtils })
// Load old snapshot - migrations run automatically
store . loadSnapshot ( oldSnapshot )
Migrations are applied automatically when loading snapshots from older versions.
Best practices
Debounce saves Avoid saving on every change. Debounce save operations to reduce load.
Handle errors Implement error handling for failed save/load operations.
Version snapshots Store version information with snapshots for backwards compatibility.
Compress data Compress snapshots before storing to reduce size.
Debouncing saves
import { useEffect , useRef } from 'react'
import { Editor } from 'tldraw'
function useDebouncedSave ( editor : Editor , delay = 1000 ) {
const timeoutRef = useRef < number >()
useEffect (() => {
const handleChange = () => {
if ( timeoutRef . current ) {
clearTimeout ( timeoutRef . current )
}
timeoutRef . current = window . setTimeout (() => {
const snapshot = editor . store . getSnapshot ()
// Save snapshot
console . log ( 'Saving...' , snapshot )
}, delay )
}
const dispose = editor . store . listen ( handleChange , { scope: 'document' })
return () => {
dispose ()
if ( timeoutRef . current ) {
clearTimeout ( timeoutRef . current )
}
}
}, [ editor , delay ])
}