# Configuring IndexedDB Persistence for Pascal Editor Scene Data

> Learn how to configure IndexedDB persistence for Pascal Editor scene data. Discover how the editor manages assets and scene graphs using IndexedDB and localStorage.

- Repository: [Pascal/editor](https://github.com/pascalorg/editor)
- Tags: how-to-guide
- Published: 2026-03-25

---

**Pascal Editor stores binary user assets in IndexedDB via `idb-keyval` while persisting the lightweight scene graph JSON to localStorage, using a custom `asset://` protocol to bridge the two storage mechanisms.**

The `pascalorg/editor` repository implements a dual-tier persistence strategy that separates heavy binary data from lightweight application state. While scene hierarchy and node data serialize to `localStorage` for rapid hydration, uploaded images, 3D models, and audio files persist in **IndexedDB** through the [`asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/asset-storage.ts) module. This architecture keeps the in-memory scene store lightweight while enabling offline asset reuse.

## Understanding the Dual Persistence Architecture

Pascal Editor manages two distinct data categories with separate storage backends:

- **Scene Graph (Nodes, Hierarchy, UI State):** Serialized as JSON to `localStorage` via [`scene.ts`](https://github.com/pascalorg/editor/blob/main/scene.ts), with undo/redo handled in-memory by **Zundo**.
- **User Assets (Images, 3D Files, Audio):** Binary blobs stored in **IndexedDB** via [`asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/asset-storage.ts) and referenced through a custom `asset://` protocol.

This separation ensures that heavy binary data never enters the Zustand store, keeping undo/redo operations fast while still allowing assets to survive browser refreshes.

## Storing Binary Assets in IndexedDB

All binary asset persistence flows through [`packages/core/src/lib/asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/lib/asset-storage.ts), which wraps IndexedDB operations using the **idb-keyval** library.

### The Asset Storage Flow

When a user uploads a file, the system executes a three-step persistence process:

1. **Write:** `saveAsset(file)` stores the `Blob` in IndexedDB under a generated UUID and returns an `asset://<uuid>` URL.
2. **Reference:** Scene nodes store this URL in properties like `ItemNode.asset.url`.
3. **Resolve:** `loadAssetUrl(url)` fetches the blob from IndexedDB, creates an `ObjectURL` (`blob:`), and caches it to avoid repeated `URL.createObjectURL` calls.

### Saving User Uploads to IndexedDB

Import `saveAsset` from the core package to persist uploaded files and receive a referenceable URL:

```typescript
import { saveAsset } from '@pascal-app/core/lib/asset-storage'

async function handleUpload(file: File) {
  // Returns "asset://d7e4c1a2-..." and stores blob in IndexedDB
  const assetUrl = await saveAsset(file)
  
  // Attach to scene node
  const newItem = ItemNode.parse({
    asset: { url: assetUrl, category: 'furniture' }
  })
  useScene.getState().createNode(newItem, parentId)
}

```

*Source:* [`packages/core/src/lib/asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/lib/asset-storage.ts) (lines 11-15)

### Resolving Asset URLs for Rendering

At render time, convert the stored `asset://` reference back to a usable blob URL:

```typescript
import { loadAssetUrl } from '@pascal-app/core/lib/asset-storage'

async function getTextureUrl(nodeAssetUrl: string): Promise<string | null> {
  // Returns "blob:https://..." or original URL if not asset://
  return await loadAssetUrl(nodeAssetUrl)
}

```

*Source:* [`packages/core/src/lib/asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/lib/asset-storage.ts) (lines 21-30)

## Persisting Scene Graph State to localStorage

Unlike assets, the scene graph itself never touches IndexedDB. Instead, [`packages/editor/src/lib/scene.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/lib/scene.ts) provides localStorage helpers for JSON serialization.

### Serializing Scene Data

Capture the current editor state and write it to localStorage:

```typescript
import { saveSceneToLocalStorage } from '@pascal-app/editor/lib/scene'

function exportCurrentScene() {
  const { nodes, rootNodeIds } = useScene.getState()
  saveSceneToLocalStorage({ nodes, rootNodeIds })
}

```

*Source:* [`packages/editor/src/lib/scene.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/lib/scene.ts) (lines 72-80)

### Restoring Editor State on Initialization

Hydrate the application on startup by loading the persisted graph and applying it to the editor:

```typescript
import { loadSceneFromLocalStorage, applySceneGraphToEditor } from '@pascal-app/editor/lib/scene'

function initApp() {
  const saved = loadSceneFromLocalStorage()
  applySceneGraphToEditor(saved) // Restores nodes + UI selection
}

```

*Source:* [`packages/editor/src/lib/scene.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/lib/scene.ts) (lines 82-90)

## State Management Architecture

The persistence strategy relies on specific middleware and store structures:

- **`useScene` Store ([`packages/core/src/store/use-scene.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/store/use-scene.ts)):** Central Zustand store managing nodes and mutations. Wrapped with **Zundo** (`zundo.temporal`) to keep the last 50 actions in memory for undo/redo, avoiding disk I/O during history navigation.
- **Viewer Preferences ([`packages/viewer/src/store/use-viewer.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/store/use-viewer.ts)):** UI state like theme, camera mode, and level mode persists via `zustand/middleware` `persist`, but this excludes scene nodes.
- **idb-keyval:** Declared in [`packages/core/package.json`](https://github.com/pascalorg/editor/blob/main/packages/core/package.json), this minimal promise-based wrapper handles all IndexedDB transactions for asset storage.

## Summary

- **Binary assets** persist in IndexedDB via [`asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/asset-storage.ts) using the `asset://` URI scheme and `idb-keyval`.
- **Scene graphs** serialize to `localStorage` as JSON through [`scene.ts`](https://github.com/pascalorg/editor/blob/main/scene.ts), enabling fast initial loads.
- **Undo/redo** operates entirely in memory via Zundo, ensuring heavy blobs never slow down history navigation.
- **Asset resolution** converts stored `asset://` URLs to temporary `blob:` URLs at runtime for rendering.

## Frequently Asked Questions

### Why does Pascal Editor use localStorage for the scene graph instead of IndexedDB?

The scene graph consists of lightweight JSON describing nodes and hierarchy. Storing it in `localStorage` provides synchronous read/write access needed for rapid state hydration, while keeping IndexedDB reserved for large binary blobs prevents the Zustand store from becoming bloated and maintains fast in-memory undo/redo performance via Zundo.

### What library handles IndexedDB operations in the Pascal Editor codebase?

The codebase uses **idb-keyval**, a minimal promise-based wrapper around the IndexedDB API. This dependency is declared in [`packages/core/package.json`](https://github.com/pascalorg/editor/blob/main/packages/core/package.json) and consumed exclusively in [`packages/core/src/lib/asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/lib/asset-storage.ts) for `saveAsset` and `loadAssetUrl` operations.

### How does the editor reference stored assets in the scene graph?

Scene nodes store string URLs in the format `asset://<uuid>` (for example, `asset://d7e4c1a2-...`). The `loadAssetUrl` function in [`asset-storage.ts`](https://github.com/pascalorg/editor/blob/main/asset-storage.ts) detects this protocol, retrieves the corresponding blob from IndexedDB, generates a temporary `blob:` URL via `URL.createObjectURL`, and caches the result to avoid repeated database lookups during rendering.

### Where is UI preference data persisted in the Pascal Editor?

User interface preferences—including theme settings, camera mode, and level mode—are persisted using Zustand's `persist` middleware in [`packages/viewer/src/store/use-viewer.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/store/use-viewer.ts). This storage mechanism is separate from both the scene graph (localStorage) and binary assets (IndexedDB).