# Data flow — excalidraw/excalidraw

> Canonical page: https://codevetter.com/unpack/excalidraw/data-flow

# Data flow — excalidraw/excalidraw

Scanned commit 31df3e6ef245c646b18a055ccf858cd6117c7768 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

A scene is an ordered list of elements plus AppState, with image bytes kept in a separate BinaryFiles map. Local edits pass through the Store, which both feeds undo history and the public onChange/onIncrement callbacks. The app then fans changes into localStorage, IndexedDB, and — when a room is open — an encrypted socket broadcast plus a Firebase transaction. Anything loaded back in goes through restoreElements/restoreAppState and, for multiplayer, reconcileElements on version plus versionNonce.
- Elements carry collab metadata on the base type: monotonically increasing version, random versionNonce for tie-breaks, fractional index (string brand) for z-order, updated timestamp, and isDeleted. index is kept aligned with array order by syncMovedIndices and syncInvalidIndices. — [packages/element/src/types.ts#L40-87](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/element/src/types.ts#L40-L87) [packages/element/src/Scene.ts#L15-19](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/element/src/Scene.ts#L15-L19)
- Local autosave is owned by LocalData.save, debounced by SAVE_TO_LOCAL_STORAGE_TIMEOUT (300ms). It writes non-deleted elements to localStorage key excalidraw and cleared app state to excalidraw-state, then puts image binaries in IndexedDB database files-db / store files-store via idb-keyval. QuotaExceededError flips localStorageQuotaExceededAtom. Saves are skipped when document.hidden or the collaboration locker is held. — [excalidraw-app/data/LocalData.ts#L49-165](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/LocalData.ts#L49-L165) [excalidraw-app/data/LocalData.ts#L204-226](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/LocalData.ts#L204-L226) [excalidraw-app/app_constants.ts#L1-7](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L1-L7) [excalidraw-app/app_constants.ts#L39-53](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L39-L53)
- The app onChange callback is the fan-out. If collabAPI.isCollaborating() it calls collabAPI.syncElements(elements). Otherwise, when LocalData.isSavePaused() is false, it calls LocalData.save and afterwards rewrites image elements whose status should become saved via newElementWith. — [excalidraw-app/App.tsx#L715-739](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/App.tsx#L715-L739)
- Cross-tab sync stores timestamps under version-dataState and version-files. updateBrowserStateVersion writes Date.now() into both localStorage and an in-memory LOCAL_STATE_VERSIONS map. On focus or visibility the app calls isBrowserStorageStateNewer and, for files, LocalData.fileStorage.getFiles for image ids not already in memory. — [excalidraw-app/data/tabSync.ts#L1-39](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/tabSync.ts#L1-L39) [excalidraw-app/App.tsx#L624-670](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/App.tsx#L624-L670)
- Socket payloads are JSON, UTF-8 encoded, then AES-GCM encrypted with the room key. Portal._broadcastSocketData emits WS_EVENTS.SERVER (server-broadcast) or SERVER_VOLATILE. broadcastScene sends only elements whose version is newer than broadcastedElementVersions unless syncAll is set, and SCENE_INIT requires syncAll. File upload is throttled at FILE_UPLOAD_TIMEOUT (300ms) and flips image status to saved so peers know to pull bytes. — [excalidraw-app/collab/Portal.tsx#L85-183](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/collab/Portal.tsx#L85-L183) [excalidraw-app/app_constants.ts#L16-21](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L16-L21) [packages/excalidraw/data/encryption.ts#L50-78](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/excalidraw/data/encryption.ts#L50-L78)
- Firebase scene documents store {sceneVersion, ciphertext: Bytes, iv: Bytes}. saveToFirebase runs a Firestore transaction on scenes/{roomId}: if the doc exists it decrypts the previous elements, reconcileElements against the local syncable set, and writes the merged ciphertext. A WeakMap FirebaseSceneVersionCache skips writes when the socket already persisted that getSceneVersion. — [excalidraw-app/data/firebase.ts#L87-116](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/firebase.ts#L87-L116) [excalidraw-app/data/firebase.ts#L174-266](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/firebase.ts#L174-L266)
- reconcileElements keeps the local element when it is being edited (editingTextElement, resizingElement, newElement), when local.version is greater, or when versions match and local.versionNonce is less than or equal to the remote nonce. Fractional indices are revalidated in dev and test (validateFractionalIndices throws); in production the check is throttled to once a minute and does not throw. — [packages/excalidraw/data/reconcile.ts#L23-77](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/excalidraw/data/reconcile.ts#L23-L77)
- Syncable elements exclude invisibly small elements. Deleted elements stay syncable only until DELETED_ELEMENT_TIMEOUT (24h) so peers can observe the deletion, then they drop out of getSyncableElements. — [excalidraw-app/data/index.ts#L46-63](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/index.ts#L46-L63) [excalidraw-app/app_constants.ts#L9](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L9)
- Startup scene load order in initializeScene: importFromLocalStorage, then optionally #json= via importFromBackend (decompress + decrypt, falling back to legacy_decodeFromBackend), then #url= via fetch + loadFromBlob, then #room= via collabAPI.startCollaboration. External scenes prompt with openConfirmModal unless the canvas is empty or the link is a collab room. Non-room imports then history.replaceState back to the origin. — [excalidraw-app/App.tsx#L217-373](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/App.tsx#L217-L373) [excalidraw-app/data/index.ts#L166-241](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/index.ts#L166-L241)
- Share-link import tries the new compressed format first (decompressData with decryptionKey). On failure legacy_decodeFromBackend slices a 12-byte IV off the front, and if that throws it retries with a zero IV for the oldest files. — [excalidraw-app/data/index.ts#L166-236](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/index.ts#L166-L236) [packages/excalidraw/data/encryption.ts#L5](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/excalidraw/data/encryption.ts#L5)
- Keys are 128-bit AES-GCM (ENCRYPTION_KEY_BITS). generateEncryptionKey uses crypto.subtle.generateKey and exports the JWK k field. getCryptoKey imports alg A128GCM. IVs are 12 random bytes from crypto.getRandomValues. Room ids are 10 random bytes hex-encoded (ROOM_ID_BYTES). — [packages/excalidraw/data/encryption.ts#L5-94](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/excalidraw/data/encryption.ts#L5-L94) [packages/common/src/constants.ts#L397](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/packages/common/src/constants.ts#L397) [excalidraw-app/data/index.ts#L68-72](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/index.ts#L68-L72) [excalidraw-app/app_constants.ts#L37](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L37)
- Collab image bytes and share-link image bytes use different Storage prefixes: FIREBASE_STORAGE_PREFIXES.collabFiles is /files/rooms and shareLinkFiles is /files/shareLinks. Cache-Control on upload is public, max-age=31536000. — [excalidraw-app/app_constants.ts#L13-35](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/app_constants.ts#L13-L35) [excalidraw-app/data/firebase.ts#L145-171](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/firebase.ts#L145-L171) [excalidraw-app/data/index.ts#L288-291](https://github.com/excalidraw/excalidraw/blob/31df3e6ef245c646b18a055ccf858cd6117c7768/excalidraw-app/data/index.ts#L288-L291)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
