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

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
StorageEnum Enum mapping to Chrome’s storage areas: local, sync, managed, and session. packages/storage/lib/base/enums.ts
BaseStorageType TypeScript interface defining the contract for storage instances. 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

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.

// 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 performs several critical operations:

  • Permission Verification: Validates that the requested storage area is declared in 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.

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, 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.
  • 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. 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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →