Menu

Local Storage and IndexedDB

Relevant source files

Purpose and Scope

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:


Storage Architecture Overview

The application employs a two-tier storage strategy to optimize for different data types and access patterns.

Storage Distribution:

Data TypeStorageKey/StoreMax SizeSerialization
ElementslocalStorageLOCAL_STORAGE_ELEMENTS~5-10 MBJSON (cleared for storage)
AppStatelocalStorageLOCAL_STORAGE_APP_STATE~5-10 MBJSON (cleared for storage)
Binary FilesIndexedDBfiles-db/files-storeBrowser dependent (~50+ MB)Base64 dataURL
Library ItemsIndexedDBexcalidraw-library-dbBrowser dependentJSON

Sources: excalidraw-app/data/LocalData.ts1-276 excalidraw-app/App.tsx620-670


LocalData Class

The LocalData class is the central coordinator for all local persistence operations, providing a unified interface for saving application state.

Save Mechanism

The save mechanism uses a debounced pattern to batch multiple rapid changes into a single storage operation:

Key Implementation Details:

  • Debounce Duration: SAVE_TO_LOCAL_STORAGE_TIMEOUT (default from constants)
  • Synchronous Check: isSavePaused() is checked synchronously before debounce
  • Callback Pattern: Accepts onFilesSaved callback for post-save actions

Save Conditions:

  1. Not during document.hidden state
  2. Not locked by collaboration (lockType: "collaboration")
  3. Changes have occurred (handled by caller)

Sources: excalidraw-app/data/LocalData.ts116-150 excalidraw-app/App.tsx620-659

Flush Operations

Critical flush points ensure data is persisted before potential data loss:

EventHandlerPurpose
window.unloadLocalData.flushSave()Before page unload
window.blurLocalData.flushSave()Before losing focus
document.visibilitychangeLocalData.flushSave()Before tab becomes hidden
beforeunloadLocalData.flushSave()Before navigation away

Sources: excalidraw-app/App.tsx561-592

Pause/Resume During Collaboration

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 Persistence

LocalStorage stores JSON-serialized application data with special cleaning for storage optimization.

Data Structure

Storage Cleaning

Before serialization, data is cleaned to remove transient properties:

Elements Cleaning (clearElementsForLocalStorage):

  • Removes collaboration-specific data
  • Strips temporary rendering state
  • Preserves core element properties

AppState Cleaning (clearAppStateForLocalStorage):

  • Removes UI-only state (open dialogs, modals)
  • Clears transient selection state
  • Preserves tool settings and preferences
  • Special handling: Closes CANVAS_SEARCH_TAB if open in default sidebar

Sources: excalidraw-app/data/LocalData.ts72-108

Storage Quota Handling

The system gracefully handles storage quota exceeded errors:

Error Detection:

User Notification:

  • Atom: localStorageQuotaExceededAtom
  • Alert: "alerts.localStorageQuotaExceeded" translation key
  • Display: Red alert banner at top of canvas

Sources: excalidraw-app/data/LocalData.ts76-112 excalidraw-app/App.tsx731 excalidraw-app/App.tsx911-915


IndexedDB File Storage

Binary files (images) are stored in IndexedDB using the idb-keyval library for simplified key-value operations.

Store Configuration

Store Creation:

Sources: excalidraw-app/data/LocalData.ts48

LocalFileManager Class

Extends FileManager to provide IndexedDB-specific file operations:

File Lifecycle

Save Process:

  1. Check if file is already saved or being saved
  2. Add to addedFiles map
  3. Update STORAGE_KEYS.VERSION_FILES for tab sync
  4. Save each file to IndexedDB with set()
  5. Track saved/errored files

Load Process:

  1. Call getMany() for batch retrieval
  2. Update lastRetrieved timestamp to current time
  3. Save updated metadata back to storage
  4. Return loaded/errored file maps

Sources: excalidraw-app/data/LocalData.ts52-225

Obsolete File Cleanup

Automatic cleanup prevents IndexedDB from growing unbounded:

Cleanup Criteria:

  • File not referenced in current elements (currentFileIds)
  • AND file not retrieved in last 24 hours (lastRetrieved > 24 * 3600 * 1000)

Trigger Points:

  • On initial scene load after loading files from storage
  • After loading files from external sources

Implementation:

Sources: excalidraw-app/data/LocalData.ts53-69 excalidraw-app/App.tsx465-467


Library Persistence

The library system (reusable element collections) uses IndexedDB with a migration path from legacy localStorage.

LibraryIndexedDBAdapter

Store Configuration:

Sources: excalidraw-app/data/LocalData.ts227-254

Legacy Migration

The LibraryLocalStorageMigrationAdapter handles migration from old localStorage-based library storage:

Migration Flow:

Migration Adapter:

  • Reads from STORAGE_KEYS.__LEGACY_LOCAL_STORAGE_LIBRARY
  • Parses JSON to LibraryItem[]
  • Clears legacy key after successful migration

Sources: excalidraw-app/data/LocalData.ts256-275 excalidraw-app/App.tsx376-381


Tab Synchronization

The application synchronizes state across browser tabs using a version-based mechanism with localStorage events.

Version Tracking

Synchronization Flow

Version Update: Each save increments a version counter in localStorage

Tab Sync Detection:

Sync Triggers:

  • window.focus event
  • document.visibilitychange event when becoming visible
  • Debounced with SYNC_BROWSER_TABS_TIMEOUT

Sync Conditions:

  • Document not hidden
  • Not currently collaborating (or collaboration disabled)
  • Browser storage version is newer than local

Sources: excalidraw-app/App.tsx502-559 excalidraw-app/data/LocalData.ts97 excalidraw-app/data/LocalData.ts209


Integration with Application Lifecycle

The storage system integrates with key application lifecycle events to ensure data persistence and consistency.

Initialization Flow

Sources: excalidraw-app/App.tsx205-335 excalidraw-app/App.tsx400-475

Save Triggers

Multiple event sources trigger save operations:

SourceEvent HandlerSave TypeNotes
Canvas changesonChange(elements, appState, files)DebouncedMain save path
Scene updatesonChange callbackDebouncedAfter any drawing operation
File status updatesInside onChange callbackImmediateUpdate element status to "saved"
Blur/HidevisibilityChangeFlushForce immediate save
NavigationbeforeUnloadFlushPrevent data loss

Save Flow in onChange:

Sources: excalidraw-app/App.tsx620-670

Collaboration Mode Interaction

During collaboration, local storage behavior changes:

Start Collaboration:

  1. LocalData.pauseSave("collaboration") - Disable local saves
  2. LocalData.fileStorage.reset() - Clear file tracking
  3. Remote sync takes over persistence

Stop Collaboration:

  1. User confirms to override local state
  2. resetBrowserStateVersions() - Ignore other tabs' state
  3. LocalData.fileStorage.reset() - Clear file tracking
  4. LocalData.resumeSave("collaboration") - Re-enable local saves
  5. Update image elements to status: "pending"
  6. Normal local persistence resumes

Sources: excalidraw-app/collab/Collab.tsx352-413 excalidraw-app/collab/Collab.tsx466-553


File Version Management

The FileManager base class (extended by LocalFileManager) provides sophisticated file version tracking to coordinate saves and loads across the distributed system.

File State Tracking

Tracking Maps:

  • fetchingFiles: Map<FileId, true> - Currently loading
  • savingFiles: Map<FileId, FileVersion> - Currently saving with version
  • savedFiles: Map<FileId, FileVersion> - Successfully saved with version
  • erroredFiles_fetch: Map<FileId, true> - Failed to load
  • erroredFiles_save: Map<FileId, FileVersion> - Failed to save with version

Version Checking:

Sources: excalidraw-app/data/FileManager.ts22-204

shouldPreventUnload Logic

Determines if navigation should be blocked due to unsaved files:

Prevention Criteria:

  • Element is initialized image element
  • Element is not deleted
  • File is currently in 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


Error Handling and Recovery

Storage Quota Exceeded

Detection: Catches DOMException with name "QuotaExceededError"

Recovery Strategy:

  1. Set localStorageQuotaExceededAtom to true
  2. Display persistent alert banner to user
  3. Continue operation (read-only mode)
  4. Allow existing data to remain accessible

User Actions:

  • Export scene to .excalidraw file
  • Clear browser data and reload
  • Use Excalidraw+ cloud storage

Sources: excalidraw-app/data/LocalData.ts101-107 excalidraw-app/App.tsx911-915

File Load/Save Errors

Error Tracking:

  • Separate maps for fetch vs save errors
  • Version-aware error tracking for saves
  • No automatic retry for errored saves

User Notification:

  • updateStaleImageStatuses() marks errored images with status: "error"
  • Error status visible in UI with icon indicator
  • User can manually retry by re-inserting image

Sources: excalidraw-app/data/FileManager.ts250-274

Tab Sync Race Conditions

Problem: Multiple tabs updating simultaneously could cause conflicts

Solution: Version-based last-write-wins strategy

  • Each write increments version counter
  • Readers only apply updates if browser version > local version
  • resetBrowserStateVersions() available to force local precedence

Sources: excalidraw-app/data/tabSync.ts (referenced), excalidraw-app/collab/Collab.tsx377