# Storage & settings

> Persistent key/value storage per card and per package, and reading and changing the settings form the app draws from your manifest.

Source: https://docs.neurosquad.ai/en/card-sdk/api/storage-settings

## Storage

A card's page has no `localStorage`, `indexedDB` or cookies — its sandbox has no origin to keep them
in. `card.storage` is the replacement: JSON values the app keeps on disk, atomically, across reloads,
suspension and restarts. No permission needed.

```ts
// This card on the canvas (instance scope).
const draft = await card.storage.get('draft', '')          // string, '' when missing
await card.storage.set('draft', `${draft}\nmore`)
await card.storage.set('runs', [{ at: Date.now(), failed: 3 }])
const runs = await card.storage.get<{ at: number; failed: number }[]>('runs')  // undefined when missing
await card.storage.delete('draft')
const keys = await card.storage.keys('run:')               // keys starting with a prefix
const { bytes, quota } = await card.storage.usage()
render(runs?.length, keys, bytes, quota)

// Shared by every copy of this card, on every canvas (package scope).
await card.storage.package.set('lastSync', Date.now())
card.storage.onChange(({ key, byInstance }) => {
  if (key === 'lastSync') render(`updated by card ${byInstance}`)
})
```

| Method | Result |
| --- | --- |
| `get(key)` | The value, or `undefined`. |
| `get(key, fallback)` | The value, or `fallback`. The result is typed like the fallback. |
| `set(key, value)` | Stores any JSON value. |
| `delete(key)` | |
| `keys(prefix?)` | `string[]` |
| `clear()` | Deletes every key of that scope. |
| `usage()` | `{ bytes, quota, keys }` |
| `onChange(handler)` | Another copy of this card wrote a **package** key: `{ scope, key, byInstance }`. |

The same methods exist on `card.storage.package`.

**Limits:** keys ≤ 256 characters, values ≤ 1 MB, ≤ 10 000 keys; 5 MB per card, 20 MB per package.
Going over fails with `QUOTA_EXCEEDED`. Writes reach the disk within about a quarter of a second
and are flushed when the app quits.

**What happens to it:** instance storage is deleted with the card. Package storage survives
uninstalling unless the user ticks **Also delete its saved data and secrets**. Storage survives
updates — keep your stored shapes readable by newer versions (store a `version` field if you
might change them).

> Save before you are unloaded: a card that is hidden for a while is suspended, and its page is
> thrown away. Use [`card.lifecycle.onSuspend`](https://docs.neurosquad.ai/en/card-sdk/api/environment#lifecycle) to write what is
> still in memory.

## Settings

Declare fields in the manifest's [`settings`](https://docs.neurosquad.ai/en/card-sdk/manifest#settings); the app draws the form
(from the card's **⋯ → Settings…**, or when you call `card.settings.open()`), validates it and
stores the values. Your card reads them:

```ts
const command = card.settings.value<string>('command') ?? 'npm test'
const all = card.settings.values                         // defaults filled in, secrets never included
const hasKey = card.settings.hasSecret('apiKey')         // true when the user saved one

card.settings.onChange((snapshot) => {
  render(snapshot.values['command'], snapshot.secrets['apiKey'])
})

// Change your own non-secret settings (a toggle in your UI, say). Validated like the form.
await card.settings.set({ compact: true })

// Open the form on the card (only while it is visible).
if (!hasKey) await card.settings.open()

render(command, all)
```

| Member | What |
| --- | --- |
| `values` | Current values, defaults filled in. Kept current. |
| `secrets` | `Record<key, boolean>`: which secret settings are set. |
| `value(key)` | One value, typed by you. |
| `hasSecret(key)` | Whether a secret setting is set. |
| `get()` | A fresh `SettingsSnapshot` from the app. |
| `set(values)` | Changes non-secret settings; every copy of the card gets `settings.changed`. Invalid values fail with `INVALID_PARAMS`. At most 60 a minute. |
| `open()` | Opens the settings form on the card. `NOT_VISIBLE` if the card is off screen. |
| `onChange(handler)` | The user saved the form, or `set` changed something. |

**Scopes.** A field with `"scope": "package"` has one value for every copy of your card; the rest are
per card.

**Secrets.** A `secret` field is typed only into the app's own dialog — in the settings form its row
shows just "set" or "not set" and opens that window-wide dialog — and stored encrypted by the app. The
card can never read it — not through `values`, not through any method. To use one, put
`{{secret:<key>}}` into a [network request header](https://docs.neurosquad.ai/en/card-sdk/api/network#secrets); the app fills it
in on its way out, only for internet hosts your card was granted (never for `localhost`). Do not build your own "paste your API key"
field inside the card: the key would then be yours to leak, and users are told that NeuroSquad
never asks for keys inside a card.
