Skip to content

Persist Data Locally

Introduction

This guide covers the @parity/product-sdk-local-storage package, which provides a LocalKvStore for storing and retrieving key-value data between sessions. Use it for device-local data like saved preferences, unsubmitted drafts, or cached responses. Data is scoped to your Product and device; it does not sync across devices or leak between Products on the same Host.

Storage options for your Product

LocalKvStore is the right tool for device-local, per-Product key-value data. When your data needs to outlive the device or be visible to other users, reach for an on-chain layer instead:

  • Local KvStore (this page): Per-Product, per-device key-value. User preferences, drafts, cached values. Not synced across devices.
  • Bulletin Chain: Content-addressed, on-chain, retained ~2 weeks by default and renewable. Content readers fetch later by hash: profile photos, published articles, app bundles. See Store Data on Chain.
  • Statement Store: Gossip-distributed, short-lived (default 30s TTL), allowance-gated. Real-time signaling between users: chat messages, presence, typing indicators. See Publish and Subscribe to Off-Chain Data.

Prerequisites

Before getting started, ensure you have:

  • A Polkadot Product project running locally (see Set Up Your Project)
  • Node.js 20 or later with ESM support (@parity/product-sdk-local-storage is ESM only)

Install the SDK

You have two installation options depending on your needs:

  • Umbrella package (recommended starting point): Install the full SDK in one command. Convenient when your Product uses several SDK features (local storage, signing, cloud storage, etc.) and bundle size is not a concern.

    npm install @parity/product-sdk
    
  • Individual package: Install only what you use. Keeps your bundle smaller and makes dependencies explicit; switch to this later as a bundle-size optimization.

    npm install @parity/product-sdk-local-storage
    

All import paths shown in this guide work with both options.

Code examples

Each snippet in this guide is a standalone file you add to your Product's source tree. The filenames match the title shown in the code block header (for example, initialize.ts, set-get-string.ts). They are not meant to be concatenated; import and use each one independently wherever it fits in your Product.

Initialize the Store

createLocalKvStore() is an async factory. You must await it before performing any read or write, because the function connects to the Host storage backend before the store is usable.

initialize.ts
import { createLocalKvStore } from '@parity/product-sdk-local-storage';
import type { LocalKvStore } from '@parity/product-sdk-local-storage';

// Connects to the Host storage backend. Must run inside a Polkadot Product;
// outside a host container this throws "Host storage unavailable".
const store: LocalKvStore = await createLocalKvStore();

createLocalKvStore() accepts an optional LocalKvStoreOptions argument. The supported option is prefix, which is covered in Namespace Keys with Prefixes.

Store String Values

Use store.set(key, value) to write a string value for a key. Use store.get(key) to read it back. If the key has not been set, get returns null.

set-get-string.ts
await store.set('network', 'paseo');

const network = await store.get('network');

if (network === null) {
  console.log('Key not found');
} else {
  console.log('Network:', network);
}

Store JSON Values

Use store.setJSON(key, value) to write any JSON-serializable value. The store handles serialization for you. Use store.getJSON<T>(key) to read it back; the generic type parameter T enables type-safe retrieval. If the key does not exist, getJSON returns null.

set-get-json.ts
interface UserPreferences {
  theme: 'light' | 'dark';
  language: string;
}

await store.setJSON('preferences', {
  theme: 'dark',
  language: 'en',
} satisfies UserPreferences);

const preferences = await store.getJSON<UserPreferences>('preferences');

if (preferences === null) {
  console.log('No preferences found');
} else {
  console.log('Theme:', preferences.theme);
  console.log('Language:', preferences.language);
}

Remove Stored Values

Use store.remove(key) to delete a key. The method does not throw if the key does not exist, so you can call it safely without first checking whether the value is present.

remove.ts
await store.remove('network');

Namespace Keys with Prefixes

Within a Product, multiple features may share the same LocalKvStore. Prefixes partition the namespace further to avoid key collisions between those features.

Pass a prefix option to createLocalKvStore() to prepend prefix: to every key operated on by that store instance. For example, createLocalKvStore({ prefix: 'feature' }) stores the key 'setting' internally as 'feature:setting'.

prefixed-store.ts
import { createLocalKvStore } from '@parity/product-sdk-local-storage';

const settingsStore = await createLocalKvStore({ prefix: 'settings' });
const cacheStore = await createLocalKvStore({ prefix: 'cache' });

// Stored internally as "settings:theme"
await settingsStore.set('theme', 'dark');

// Stored internally as "cache:lastBlock"
await cacheStore.set('lastBlock', '21540000');

The Host-enforced Product-level namespace is separate from any developer-defined prefix. The Host's Product namespace is applied on top of your prefix, so a key 'setting' in a { prefix: 'feature' } store ends up stored as something like 'myproduct.dot:feature:setting', without you needing to construct that path yourself.

Use React Hooks

If your Product is a React application, @parity/product-sdk provides hooks that integrate local storage directly into the React component lifecycle. The hooks read the current value on mount and re-render the component whenever it changes.

Wrap your application in ProductSDKProvider once at the root, then call the hooks anywhere inside the tree:

providers.tsx
'use client';

import { ProductSDKProvider } from '@parity/product-sdk/react';
import type { ReactNode } from 'react';

export function Providers({ children }: { children: ReactNode }) {
  return (
    <ProductSDKProvider
      name="my-product"
      fallback={<div>Initializing SDK</div>}
    >
      {children}
    </ProductSDKProvider>
  );
}

useLocalStorageString manages a plain string value. useLocalStorage<T> manages a JSON-serializable value typed by T. Both hooks return a tuple of [currentValue, setValue, { loading, error }]:

local-storage-hooks.tsx
'use client';

import {
  useLocalStorage,
  useLocalStorageString,
} from '@parity/product-sdk/react';

interface UserPreferences {
  theme: 'light' | 'dark';
  language: string;
}

function StorageDemo() {
  const [network, setNetwork, { loading: networkLoading }] =
    useLocalStorageString('network');

  const [
    preferences,
    setPreferences,
    { loading: prefsLoading, error: prefsError },
  ] = useLocalStorage<UserPreferences>('preferences');

  return (
    <div>
      <p>Network: {networkLoading ? 'loading…' : (network ?? 'not set')}</p>
      <button onClick={() => setNetwork('paseo')}>Set network</button>

      <p>
        Theme: {prefsLoading ? 'loading…' : (preferences?.theme ?? 'not set')}
      </p>
      <button onClick={() => setPreferences({ theme: 'dark', language: 'en' })}>
        Set preferences
      </button>

      {prefsError && <p>Error: {prefsError.message}</p>}
    </div>
  );
}

To remove a key or clear all storage for your Product, access app.localStorage from useProductSDK() directly:

remove-and-clear.tsx
'use client';

import { useProductSDK } from '@parity/product-sdk/react';

function StorageActions() {
  const app = useProductSDK();

  async function handleRemove() {
    await app.localStorage.remove('network');
  }

  async function handleClear() {
    // Removes every key scoped to this Product.
    await app.localStorage.clear();
  }

  return (
    <div>
      <button onClick={handleRemove}>Remove &quot;network&quot;</button>
      <button onClick={handleClear}>Clear all</button>
    </div>
  );
}

Note

app.localStorage.clear() removes every key scoped to your Product. It is equivalent to calling remove() on each key individually.

Limitations

  • Storage is not synced across devices. Values written on one Host instance are not visible on another.
  • app.localStorage.clear() and store.remove() are scoped to your Product. You cannot read or modify another Product's keys.
  • React hooks (useLocalStorage, useLocalStorageString) are only available via the umbrella package @parity/product-sdk. The standalone @parity/product-sdk-local-storage package exposes no React hooks.
  • createLocalKvStore() requires a Host backend. Running outside a host container, it throws Host storage unavailable; there is no browser localStorage fallback. Run your Product inside Polkadot Desktop to use local storage.

Where to Go Next

  • Guide Store Data on Chain


    When data must outlive the device and be fetchable by anyone, move it to the Bulletin Chain.

    Store Data on Chain

  • Guide Deploy Your Product


    You've covered the capabilities; ship your Product to a live .dot name with the Quick Start deploy routes.

    Quick Start

  • External Product SDK API Reference


    The full product-sdk surface beyond this recipe: every package, class, and method.

    Visit Site

Last update: June 16, 2026
| Created: June 16, 2026