# Settings & Persistent State

Source: https://inspectorlab.dev/settings-storage

> For the complete documentation index, see [llms.txt](https://inspectorlab.dev/llms.txt).

# Settings & Persistent State

Inspector Lab stores user preferences in Chrome's local storage, enabling consistent behavior across sessions and synchronization between the popup and the injected inspector. All settings are validated on read to ensure type safety, and storage access gracefully handles both modern promise-based and legacy callback-based Chrome APIs.

## Storage Architecture

Settings are persisted in `chrome.storage.local`, which is extension-private and inaccessible to web content. This ensures user preferences remain secure and isolated from the inspected page.

The system uses a validation-on-read model:
- **Write layer**: The popup writes settings directly to storage.
- **Read layer**: The injected inspector validates values on retrieval, treating malformed or missing keys with strict defaults (e.g., `!== false` checks ensure no accidental feature activation).

This approach keeps the inspector resilient—a corrupted or missing setting never crashes the UI; it simply falls back to a sensible default.

## Theme Settings

Inspector Lab supports two independent theme configurations:

### Custom Theme Toggle

Controls whether Inspector Lab uses its own branding or adopts the Chrome DevTools appearance.

<Field value="CUSTOM_THEME_STORAGE_KEY" type="string">
The storage key: `"customInspectorTheme"`. Defaults to `true` (branded theme enabled).
</Field>

**Reading the setting:**
```ts
const enabled = await readCustomThemeSetting(); // Promise<boolean>
```

When no value is stored, the function returns `true`, activating the branded theme by default. Only an explicit `false` written by the popup will switch to the DevTools look.

**Saving the setting:**
```ts
const success = await saveCustomThemeSetting(true); // Promise<boolean>
```

Resolves to `false` if storage rejects the write, allowing the caller to roll back the UI. Best-effort persistence—failure is non-fatal.

**Watching for changes:**
```ts
const cleanup = watchCustomThemeSetting((enabled) => {
  console.log("Theme toggled:", enabled);
});
// Later:
cleanup();
```

This is called from a React effect in the injected inspector. The watcher gracefully tolerates browsers that don't implement `chrome.storage.onChanged`; a missing namespace returns a no-op cleanup function rather than throwing, preventing the entire inspector from unmounting.

### Color Scheme Setting

Controls the light/dark mode preference, mirrored from the popup's toggle to the injected inspector. The popup's own UI state lives in its localStorage, which content scripts cannot access.

<Field value="COLOR_SCHEME_STORAGE_KEY" type="string">
The storage key: `"inspectorColorScheme"`. Allowed values: `"light"` | `"dark"` | `null`.
</Field>

**Reading the setting:**
```ts
type InspectorColorScheme = "light" | "dark";

const scheme = await readColorSchemeSetting(); // Promise<InspectorColorScheme | null>
```

When no choice has been made, the function returns `null`, and the inspector falls back to the operating system preference.

**Saving the setting:**
```ts
await saveColorSchemeSetting("dark"); // Promise<void>
```

Best-effort: if storage rejects, the inspector continues to use the OS preference. Invalid values are rejected on read, so corrupted storage never activates an unintended mode.

**Watching for changes:**
```ts
const cleanup = watchColorSchemeSetting((scheme) => {
  console.log("Color scheme changed:", scheme); // "light" | "dark" | null
});
// Later:
cleanup();
```

## Storage API Resilience

Inspector Lab implements a dual-callback/promise layer for Chrome storage access, ensuring compatibility with both modern (promise-based) and legacy (callback-only) Chrome extension runtimes. The resilient storage plumbing is shared with the diagnostics log.

### `storageGet(key)`

Reads a value from `chrome.storage.local`, resolving whichever of callback or returned promise settles first:

```ts
function storageGet(key: string): Promise<Record<string, unknown>>
```

- Never rejects; always resolves to an object (empty `{}` on failure).
- Automatically marks `chrome.runtime.lastError` as handled to suppress warnings.
- Falls back gracefully if the API throws or is unavailable.

### `storageSet(items)`

Writes values to `chrome.storage.local`, resolving to a boolean indicating success:

```ts
function storageSet(items: Record<string, unknown>): Promise<boolean>
```

- Resolves to `false` if storage rejects, allowing callers to react to failure.
- Resolves to `true` only if the write succeeds.
- Never rejects.

### `watchStorage(listener)`

Subscribes to storage changes and returns a cleanup function:

```ts
function watchStorage(
  listener: (
    changes: Record<string, chrome.storage.StorageChange>,
    area: string,
  ) => void,
): () => void
```

- Gracefully tolerates browsers without `chrome.storage.onChanged`.
- Always returns a cleanup function (a no-op if registration failed).
- Prevents errors from propagating into React's effect cleanup, avoiding accidental unmounts.

## Validation & Defaults

All settings are validated on read using strict comparison. This ensures type safety even if storage is manually corrupted or migrated:

- **Custom theme**: Strict `!== false` check; any non-false value (including `undefined`, `null`, `1`, or a string) activates the branded theme.
- **Color scheme**: Exact match to `"light"` or `"dark"`; any other value returns `null`, falling back to OS preference.

<Callout type="note">
Storage is extension-private and cannot be accessed by web content, so these settings remain secure even when inspecting untrusted pages.
</Callout>

## Related Pages

- **[Theming & Styling](/theming)** — Details on the branded theme and color scheme design.
- **[Installation & Setup](/installation)** — How Inspector Lab initializes and persists state on first load.
- **[Architecture Overview](/architecture)** — How the popup, content script, and injected inspector communicate.
