Skip to main content

useExtensionStorage

Read and write the extension's shared key-value storage. Changes are persisted to the database and broadcast to all other surfaces in real time via WebSocket.

Signature

const [storage, setStorage] = useExtensionStorage<T>();

Parameters

This hook takes no parameters.

Return value

Returns a tuple of two values:

IndexTypeDescription
0TCurrent storage object (defaults to {} on first load)
1(storage: T) => voidSetter function — replaces the entire storage object

Example

import { Lumio, CompactView, Toggle, TextField, useExtensionStorage } from "@zaflun/lumio-sdk";

interface MyStorage {
visible: boolean;
homeTeam: string;
awayTeam: string;
homeScore: number;
awayScore: number;
}

function Settings() {
const [storage, setStorage] = useExtensionStorage<MyStorage>();

// Safely read with defaults
const visible = storage.visible ?? true;
const homeTeam = storage.homeTeam ?? "";

return (
<CompactView title="Scoreboard">
<Toggle
label="Show overlay"
checked={visible}
onChange={(checked) => setStorage({ ...storage, visible: checked })}
/>
<TextField
label="Home team"
value={homeTeam}
onChange={(value) => setStorage({ ...storage, homeTeam: value })}
/>
</CompactView>
);
}

Lumio.render(<Settings />, { target: "editor" });

Reading in the layer

The same storage is accessible in the layer surface:

import { Lumio, Box, Text, useExtensionStorage } from "@zaflun/lumio-sdk";

function Overlay() {
const [storage] = useExtensionStorage<MyStorage>();

if (!storage.visible) return null;

return (
<Box style={{ position: "absolute", top: 20, right: 20 }}>
<Text content={`${storage.homeTeam} ${storage.homeScore ?? 0} - ${storage.awayScore ?? 0} ${storage.awayTeam}`} />
</Box>
);
}

Lumio.render(<Overlay />, { target: "layer" });

Spread rule

The setter replaces the entire storage object. Always spread the current value:

// Correct: preserves all other keys
setStorage({ ...storage, visible: true });

// Wrong: deletes all other keys!
setStorage({ visible: true });

Real-time sync

All surfaces receive storage updates instantly via WebSocket — there is no polling. When the editor calls setStorage(), the layer re-renders within milliseconds.

Persistence

Storage is persisted to PostgreSQL. It survives:

  • OBS Browser Source reloads
  • Page refreshes on the interactive surface
  • Extension updates (the data schema is not versioned — ensure backward compatibility when changing field names)

Size limit

The storage object must serialize to under 64 KB of JSON. For larger structured data, use server functions.

Notes

  • The type parameter T is optional but strongly recommended
  • On first load, storage is {} — always use ?? defaultValue when reading fields
  • setStorage() applies an optimistic update locally before confirming with the server
  • Storage is scoped per installation — different overlay installs of the same extension have independent storage objects