This document describes the local browser storage architecture used by the Excalidraw application to persist drawing data, application state, and binary files. The system uses a dual-storage approach: localStorage for serializable data (elements and app state) and IndexedDB for binary file storage (images and library items).
For information about:
The application employs a two-tier storage strategy to optimize for different data types and access patterns.
Storage Distribution:
| Data Type | Storage | Key/Store | Max Size | Serialization |
|---|---|---|---|---|
| Elements | localStorage | LOCAL_STORAGE_ELEMENTS | ~5-10 MB | JSON (cleared for storage) |
| AppState | localStorage | LOCAL_STORAGE_APP_STATE | ~5-10 MB | JSON (cleared for storage) |
| Binary Files | IndexedDB | files-db/files-store | Browser dependent (~50+ MB) | Base64 dataURL |
| Library Items | IndexedDB | excalidraw-library-db | Browser dependent | JSON |
Sources: excalidraw-app/data/LocalData.ts1-276 excalidraw-app/App.tsx620-670
The LocalData class is the central coordinator for all local persistence operations, providing a unified interface for saving application state.
The save mechanism uses a debounced pattern to batch multiple rapid changes into a single storage operation:
Key Implementation Details:
SAVE_TO_LOCAL_STORAGE_TIMEOUT (default from constants)isSavePaused() is checked synchronously before debounceonFilesSaved callback for post-save actionsSave Conditions:
document.hidden statelockType: "collaboration")Sources: excalidraw-app/data/LocalData.ts116-150 excalidraw-app/App.tsx620-659
Critical flush points ensure data is persisted before potential data loss:
| Event | Handler | Purpose |
|---|---|---|
window.unload | LocalData.flushSave() | Before page unload |
window.blur | LocalData.flushSave() | Before losing focus |
document.visibilitychange | LocalData.flushSave() | Before tab becomes hidden |
beforeunload | LocalData.flushSave() | Before navigation away |
Sources: excalidraw-app/App.tsx561-592
The Locker class prevents local saves during real-time collaboration to avoid conflicts:
Sources: excalidraw-app/data/LocalData.ts152-164 excalidraw-app/collab/Collab.tsx501-502 excalidraw-app/collab/Collab.tsx411
LocalStorage stores JSON-serialized application data with special cleaning for storage optimization.
Before serialization, data is cleaned to remove transient properties:
Elements Cleaning (clearElementsForLocalStorage):
AppState Cleaning (clearAppStateForLocalStorage):
CANVAS_SEARCH_TAB if open in default sidebarSources: excalidraw-app/data/LocalData.ts72-108
The system gracefully handles storage quota exceeded errors:
Error Detection:
User Notification:
localStorageQuotaExceededAtom"alerts.localStorageQuotaExceeded" translation keySources: excalidraw-app/data/LocalData.ts76-112 excalidraw-app/App.tsx731 excalidraw-app/App.tsx911-915
Binary files (images) are stored in IndexedDB using the idb-keyval library for simplified key-value operations.
Store Creation:
Sources: excalidraw-app/data/LocalData.ts48
Extends FileManager to provide IndexedDB-specific file operations:
Save Process:
addedFiles mapSTORAGE_KEYS.VERSION_FILES for tab syncset()Load Process:
getMany() for batch retrievallastRetrieved timestamp to current timeSources: excalidraw-app/data/LocalData.ts52-225
Automatic cleanup prevents IndexedDB from growing unbounded:
Cleanup Criteria:
currentFileIds)lastRetrieved > 24 * 3600 * 1000)Trigger Points:
Implementation:
Sources: excalidraw-app/data/LocalData.ts53-69 excalidraw-app/App.tsx465-467
The library system (reusable element collections) uses IndexedDB with a migration path from legacy localStorage.
Store Configuration:
Sources: excalidraw-app/data/LocalData.ts227-254
The LibraryLocalStorageMigrationAdapter handles migration from old localStorage-based library storage:
Migration Flow:
Migration Adapter:
STORAGE_KEYS.__LEGACY_LOCAL_STORAGE_LIBRARYLibraryItem[]Sources: excalidraw-app/data/LocalData.ts256-275 excalidraw-app/App.tsx376-381
The application synchronizes state across browser tabs using a version-based mechanism with localStorage events.
Version Update: Each save increments a version counter in localStorage
Tab Sync Detection:
Sync Triggers:
window.focus eventdocument.visibilitychange event when becoming visibleSYNC_BROWSER_TABS_TIMEOUTSync Conditions:
Sources: excalidraw-app/App.tsx502-559 excalidraw-app/data/LocalData.ts97 excalidraw-app/data/LocalData.ts209
The storage system integrates with key application lifecycle events to ensure data persistence and consistency.
Sources: excalidraw-app/App.tsx205-335 excalidraw-app/App.tsx400-475
Multiple event sources trigger save operations:
| Source | Event Handler | Save Type | Notes |
|---|---|---|---|
| Canvas changes | onChange(elements, appState, files) | Debounced | Main save path |
| Scene updates | onChange callback | Debounced | After any drawing operation |
| File status updates | Inside onChange callback | Immediate | Update element status to "saved" |
| Blur/Hide | visibilityChange | Flush | Force immediate save |
| Navigation | beforeUnload | Flush | Prevent data loss |
Save Flow in onChange:
Sources: excalidraw-app/App.tsx620-670
During collaboration, local storage behavior changes:
Start Collaboration:
LocalData.pauseSave("collaboration") - Disable local savesLocalData.fileStorage.reset() - Clear file trackingStop Collaboration:
resetBrowserStateVersions() - Ignore other tabs' stateLocalData.fileStorage.reset() - Clear file trackingLocalData.resumeSave("collaboration") - Re-enable local savesstatus: "pending"Sources: excalidraw-app/collab/Collab.tsx352-413 excalidraw-app/collab/Collab.tsx466-553
The FileManager base class (extended by LocalFileManager) provides sophisticated file version tracking to coordinate saves and loads across the distributed system.
Tracking Maps:
fetchingFiles: Map<FileId, true> - Currently loadingsavingFiles: Map<FileId, FileVersion> - Currently saving with versionsavedFiles: Map<FileId, FileVersion> - Successfully saved with versionerroredFiles_fetch: Map<FileId, true> - Failed to loaderroredFiles_save: Map<FileId, FileVersion> - Failed to save with versionVersion Checking:
Sources: excalidraw-app/data/FileManager.ts22-204
Determines if navigation should be blocked due to unsaved files:
Prevention Criteria:
savingFiles map (actively being saved)Why this matters: Prevents users from losing files that are mid-upload to persistent storage, ensuring data integrity even if the save operation hasn't completed.
Sources: excalidraw-app/data/FileManager.ts174-182 excalidraw-app/App.tsx595-618
Detection: Catches DOMException with name "QuotaExceededError"
Recovery Strategy:
localStorageQuotaExceededAtom to trueUser Actions:
.excalidraw fileSources: excalidraw-app/data/LocalData.ts101-107 excalidraw-app/App.tsx911-915
Error Tracking:
User Notification:
updateStaleImageStatuses() marks errored images with status: "error"Sources: excalidraw-app/data/FileManager.ts250-274
Problem: Multiple tabs updating simultaneously could cause conflicts
Solution: Version-based last-write-wins strategy
resetBrowserStateVersions() available to force local precedenceSources: excalidraw-app/data/tabSync.ts (referenced), excalidraw-app/collab/Collab.tsx377
Refresh this wiki