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 [value, setValue] = useExtensionStorage<T>(key, initialValue);

Parameters​

ParameterTypeDescription
keystringStorage key. Not empty, at most 256 bytes (UTF-8), no :
initialValueTValue returned until the stored value has loaded, and while the key was never stored

Call the hook once per key. Each key is stored and synced on its own.

Return value​

Returns a tuple of two values:

IndexTypeDescription
0TCurrent value of key (initialValue until a stored value arrives)
1(value: T) => voidSetter - writes the new value of key; other keys are not touched

Example​

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

function Settings() {
const [visible, setVisible] = useExtensionStorage("visible", true);
const [homeTeam, setHomeTeam] = useExtensionStorage("homeTeam", "");

return (
<CompactView title="Scoreboard">
<Toggle label="Show overlay" checked={visible} onChange={setVisible} />
<TextField label="Home team" value={homeTeam} onChange={setHomeTeam} />
</CompactView>
);
}

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

Reading in the layer​

The same keys are readable in the layer surface:

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

function Overlay() {
const [visible] = useExtensionStorage("visible", true);
const [homeTeam] = useExtensionStorage("homeTeam", "");
const [awayTeam] = useExtensionStorage("awayTeam", "");
const [homeScore] = useExtensionStorage("homeScore", 0);
const [awayScore] = useExtensionStorage("awayScore", 0);

if (!visible) return null;

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

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

Grouping values under one key​

Values that always change together can share one key. The setter then replaces that key's whole value, so spread the current one:

interface Score {
home: number;
away: number;
}

const [score, setScore] = useExtensionStorage<Score>("score", { home: 0, away: 0 });

// Correct: keeps `away`
setScore({ ...score, home: score.home + 1 });

// Wrong: drops `away` from the stored value
setScore({ home: 1 } as Score);

Keep independent settings on separate keys: every key is synced on its own, so two writes to different keys never overwrite each other.

Real-time sync​

All surfaces receive storage updates instantly via WebSocket - there is no polling. When the editor calls a setter, the layer re-renders within milliseconds. Layers read storage but do not write it - see Shared Storage.

Persistence​

Storage is persisted to PostgreSQL. It survives:

  • Browser Source reloads
  • Editor panel reloads
  • Extension updates (the data schema is not versioned - ensure backward compatibility when changing field names)

Size limit​

One value must serialize to at most 64 KiB of JSON; an installation holds at most 1 000 keys and 1 MiB in total (see Shared Storage). For larger structured data, use server functions.

Notes​

  • T is inferred from initialValue; pass it explicitly for object values
  • Until the stored value has loaded, and for a key that was never stored, the hook returns initialValue
  • The setter 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