# How to Use the Storage Package to Persist Data with chrome.storage in React Vite Extensions

> Learn to persist data with chrome.storage in React Vite extensions using the storage package. This guide offers a type-safe, promise-based approach for efficient data management.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: how-to-guide
- Published: 2026-03-05

---

**The `storage` package in `jonghakseo/chrome-extension-boilerplate-react-vite` provides a type-safe, promise-based wrapper around Chrome’s native `chrome.storage` API through the `createStorage` factory function.**

The repository ships with a dedicated workspace package (`packages/storage`) that abstracts the complexity of cross-context state synchronization in Chrome extensions. Whether you are building a popup, content script, or background service worker, this utility allows you to persist data with chrome.storage using a modern React-friendly interface while maintaining full TypeScript safety.

## Core Architecture

The storage layer is built around four key components that handle serialization, permission validation, and live updates across extension contexts.

| Component | Responsibility | Source File |
|-----------|----------------|-------------|
| **`createStorage`** | Factory function that returns a storage object with `get`, `set`, `getSnapshot`, and `subscribe` methods. Handles serialization, fallback values, and live-update listeners. | [`packages/storage/lib/base/base.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/base.ts) |
| **`StorageEnum`** | Enum mapping to Chrome’s storage areas: `local`, `sync`, `managed`, and `session`. | [`packages/storage/lib/base/enums.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/enums.ts) |
| **`BaseStorageType`** | TypeScript interface defining the contract for storage instances. | [`packages/storage/lib/types.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/types.ts) |
| **`example-theme-storage`** | Reference implementation demonstrating theme state persistence with toggle helpers. | [`packages/storage/lib/impl/example-theme-storage.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/impl/example-theme-storage.ts) |

When `liveUpdate` is enabled, the package registers a listener on `chrome.storage.onChanged` to ensure that every extension context—popup, options page, background script, or content script—receives the latest value automatically without manual polling.

## Creating a Storage Instance

To persist data with chrome.storage, define your data shape and invoke `createStorage` with a unique key, default fallback values, and configuration options.

```typescript
// src/storage/settings-storage.ts
import { createStorage, StorageEnum } from '@chrome-extension-boilerplate/storage/lib/base/index.js';
import type { BaseStorageType } from '@chrome-extension-boilerplate/storage/lib/types.js';

export type SettingsState = {
  darkMode: boolean;
  notificationsEnabled: boolean;
};

export const settingsStorage: BaseStorageType<SettingsState> = createStorage<SettingsState>(
  'app-settings',
  { darkMode: false, notificationsEnabled: true },
  {
    storageEnum: StorageEnum.Local,
    liveUpdate: true,
  }
);

// Optional helper functions
export const toggleDarkMode = async () => {
  await settingsStorage.set(prev => ({
    ...prev,
    darkMode: !prev.darkMode,
  }));
};

```

The `createStorage` function in [`packages/storage/lib/base/base.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/base.ts) performs several critical operations:

- **Permission Verification**: Validates that the requested storage area is declared in [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json) before read/write operations.
- **Session Access Control**: For `StorageEnum.Session`, optionally calls `chrome.storage.session.setAccessLevel` when `sessionAccessForContentScripts` is enabled.
- **State Resolution**: The `set` method accepts either a concrete value or an updater function `(prev) => newVal`, supporting both synchronous and asynchronous updaters through the internal `updateCache` helper.

## Consuming Storage in React Components

Integrate the storage instance into React components using the `subscribe` method for real-time synchronization and `get` for initial hydration.

```tsx
import { useEffect, useState } from 'react';
import { settingsStorage, toggleDarkMode } from '@/storage/settings-storage';

export default function SettingsPanel() {
  const [settings, setSettings] = useState<SettingsState>({
    darkMode: false,
    notificationsEnabled: true,
  });

  // Initial load from chrome.storage
  useEffect(() => {
    settingsStorage.get().then(setSettings);
  }, []);

  // Subscribe to external changes (liveUpdate)
  useEffect(() => {
    const unsubscribe = settingsStorage.subscribe(() => {
      settingsStorage.get().then(setSettings);
    });
    return unsubscribe;
  }, []);

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={settings.darkMode}
          onChange={toggleDarkMode}
        />
        Enable Dark Mode
      </label>
      
      <button onClick={() => settingsStorage.set({ darkMode: false, notificationsEnabled: false })}>
        Reset to Defaults
      </button>
    </div>
  );
}

```

**Key methods available on the storage instance:**

- **`get()`**: Returns a `Promise<D>` resolving to the current stored value or fallback default.
- **`set(valueOrUpdater)`**: Persists data via `chrome.storage` and notifies all subscribers. Accepts plain values or updater functions.
- **`subscribe(listener)`**: Registers a callback invoked on any local `set` call or remote `chrome.storage.onChanged` event when `liveUpdate` is true.
- **`getSnapshot()`**: Returns the in-memory cache value synchronously, useful for non-async reads within the same execution context.

## Configuration Options

The third argument to `createStorage` accepts an options object that controls persistence behavior and cross-context synchronization.

| Option | Type | Description | Typical Usage |
|--------|------|-------------|---------------|
| `storageEnum` | `StorageEnum` | Selects the Chrome storage area. Defaults to `Local`. | `StorageEnum.Sync` for cloud-synced settings; `StorageEnum.Session` for temporary data |
| `liveUpdate` | `boolean` | Enables automatic synchronization via `chrome.storage.onChanged` listeners. | `true` when multiple extension pages need real-time state updates |
| `sessionAccessForContentScripts` | `boolean` | Grants content scripts access to session storage (requires manifest permission). | `true` only when content scripts need `StorageEnum.Session` |
| `serialization` | `{ serialize, deserialize }` | Custom transformation functions for complex data types. | `{ serialize: JSON.stringify, deserialize: JSON.parse }` for non-primitive values |

As implemented in [`packages/storage/lib/base/base.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/base.ts), the package automatically handles JSON serialization by default, but custom serializers can be injected for encryption, compression, or schema validation scenarios.

## Summary

- The **storage package** wraps `chrome.storage` with a type-safe, promise-based API centered on the `createStorage` factory function defined in [`packages/storage/lib/base/base.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/base.ts).
- **Live synchronization** across popup, background, and content script contexts is achieved by setting `liveUpdate: true`, which wires into Chrome’s native `onChanged` events.
- The **updater function pattern** in `set()` supports React-style state updates, accepting either direct values or functions receiving the previous state.
- **Storage areas** are selected via `StorageEnum` (Local, Sync, Session, Managed) with automatic permission validation against the extension manifest.

## Frequently Asked Questions

### How do I migrate from direct `chrome.storage` calls to the storage package?

Replace imperative `chrome.storage.local.get/set` calls with a typed `createStorage` instance. Define your state interface, provide fallback defaults, and use the returned `get` and `set` methods. The package handles serialization and error boundaries automatically, whereas native API calls require manual JSON parsing and error handling.

### Can content scripts access session storage using this package?

Yes, but you must enable `sessionAccessForContentScripts: true` in the configuration options and declare the `storage` permission in your [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json). The package automatically invokes `chrome.storage.session.setAccessLevel` once per extension lifetime to grant access when this option is configured.

### What is the difference between `get()` and `getSnapshot()`?

`get()` always retrieves the latest value from `chrome.storage` asynchronously, ensuring consistency across extension restarts. `getSnapshot()` returns the in-memory cached value immediately without async overhead, making it suitable for synchronous reads during the same session where the cache is already warm.

### How does `liveUpdate` keep extension contexts synchronized?

When `liveUpdate` is enabled, `createStorage` registers a listener on `chrome.storage.onChanged` in [`packages/storage/lib/base/base.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/storage/lib/base/base.ts). This listener fires whenever any extension context (popup, options page, or background script) modifies the stored value, triggering all local subscribers to refresh their state automatically.