> For the complete documentation index, see [llms.txt](https://docs.getvenora.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.getvenora.tech/plugin-api/plugin-settings-storage.md).

# Plugin settings and storage

Part of the [Community Plugin API specification](/plugin-api.md). The key words MUST, SHOULD, and MAY are used as in RFC 2119.

Venora gives each plugin two separate places for data.

|                           | Settings                                  | Storage               |
| ------------------------- | ----------------------------------------- | --------------------- |
| Holds                     | The user's choices about the plugin       | The plugin's own data |
| Declared in `plugin.json` | Yes                                       | No                    |
| Written by                | The user, in the plugin's settings dialog | The plugin            |
| Read by the plugin with   | `venora.settings.get`                     | `venora.storage.get`  |
| Permission                | `storage`                                 | `storage`             |
| Status                    | Implemented                               | Implemented           |

Settings and storage never overlap:

* `venora.storage` cannot read or change settings;
* `venora.settings` cannot read storage.

## Scoping: one plugin, its own data

These rules apply to both settings and storage, and are mandatory.

1. Settings and storage MUST be addressed by the plugin ID that Venora bound to the plugin's runtime instance. No method takes a plugin ID, and a plugin MUST NOT be able to name another plugin's settings or storage, by any means.
2. A plugin MUST NOT be able to read, write, list, detect, or receive change events for another plugin's settings or storage. This includes knowing whether another plugin exists.
3. Settings and storage MUST be kept apart from:

   * Venora's own state, such as installed plugins and enabled flags;
   * Fermo's storage.

   No key a plugin chooses can reach any of these.

## Settings

### Declaring settings

Settings are declared in `plugin.json` as an array. A plugin that declares at least one valid setting gets a settings button on its card.

```json
"settings": [
  {"key": "size", "name": "Avatar size", "description": "In pixels.", "type": "number", "default": 16, "min": 12, "max": 32, "step": 1},
  {"key": "theme", "name": "Theme", "type": "select", "default": "dark", "options": ["dark", {"value": "light", "label": "Light mode"}]},
  {"key": "enabled", "name": "Show badges", "type": "boolean", "default": true},
  {"key": "label", "name": "Label", "type": "string", "default": ""}
]
```

| Property             | Required | Applies to | Rules                                                                                                                                      |
| -------------------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `key`                | Yes      | All        | A string of 1–100 characters, unique within the plugin                                                                                     |
| `type`               | Yes      | All        | `boolean`, `string`, `number`, or `select`                                                                                                 |
| `name`               | No       | All        | Label in the dialog. Defaults to `key`.                                                                                                    |
| `description`        | No       | All        | Plain-text help shown under the label                                                                                                      |
| `default`            | No       | All        | Returned until the user saves a value. It MUST match the type: within `min`/`max` for `number`, and one of the option values for `select`. |
| `min`, `max`, `step` | No       | `number`   | Numbers, with `min` no greater than `max` and `step` greater than 0. The dialog limits input to `min`–`max`.                               |
| `options`            | Yes      | `select`   | A non-empty array. Each item is a value (string, number, or boolean) or `{value, label}`, where `label` is an optional string.             |

A plugin whose `settings` break any of these rules is not installed, and the error lists every problem. Plugins installed before these rules were enforced keep working: any of their definitions that Venora cannot use (a missing `key`, an unknown `type`, or a `select` with no `options`) are left out, and for a repeated key only the first definition is used.

### Reading settings

`venora.settings.get({key})`, which is `venora.request("settings.get", {key})`, returns:

1. the value the user saved, if there is one;
2. otherwise the declared `default`;
3. otherwise `null`.

Only declared keys can be read. Any other key rejects with `invalid_payload`.

| Type      | Value returned                                                 |
| --------- | -------------------------------------------------------------- |
| `boolean` | `true` or `false`                                              |
| `string`  | A string                                                       |
| `number`  | A finite number, limited to `min`/`max` when the user saves it |
| `select`  | One of the declared option values, with its original type      |

**Stored values and updates.**

* A value the user saved under an older version of the plugin may no longer match the current definition, for example if an update changed its type or options.
* In that case Venora returns the declared default, not the stale value. If the default is also not valid, it returns `null`. The settings dialog shows the same value.

### Writing settings

Only the user writes settings in version 1, through the settings dialog. There is no `settings.set`.

* Settings are the user's decisions about a plugin, so a plugin cannot override them.
* Data the plugin manages itself belongs in storage.

### Change events

When the user changes a setting while the plugin is running, the plugin receives `settings.changed` with `{key, value}`, if it has subscribed. That needs the `storage` permission.

* The event goes **only** to the plugin that owns the setting.
* It is not sent at startup.
* It is not sent for changes made while the plugin was not running.

A plugin SHOULD read its settings during initialization and then follow `settings.changed`.

### Persistence

Settings persist until the plugin is uninstalled. They are kept when the plugin is disabled, enabled, or updated. They are stored in the browser profile's extension storage. They are not synced between devices and are not tied to a particular Fermo account.

## Storage

### Methods

All four need the `storage` permission.

| Method                             | Capability       | Behaviour                                                                          |
| ---------------------------------- | ---------------- | ---------------------------------------------------------------------------------- |
| `venora.storage.get({key})`        | `storage.get`    | Resolves with the stored value, or `null` if the key is not set                    |
| `venora.storage.set({key, value})` | `storage.set`    | Stores `value` under `key`, replacing any previous value, and resolves with `null` |
| `venora.storage.remove({key})`     | `storage.remove` | Deletes `key`. Resolves with `null`, including when it was not set.                |
| `venora.storage.clear()`           | `storage.clear`  | Deletes all of this plugin's keys, and nothing else. Resolves with `null`.         |

There is no method that lists keys. A plugin that needs a list can keep an index under a key of its own.

### Keys and values

* **Keys** are strings of 1–100 characters. Any characters are allowed, and keys are case-sensitive. A key names data only within the calling plugin, so plugins cannot clash.
* **Values** are plain data, following the rules in [plugin-api.md §4.1](/plugin-api.md#41-arguments): `null`, booleans, finite numbers, strings, arrays, and plain objects.
  * `get` returns a copy that is equal (deep equality) to what was stored.
  * Storing `null` is allowed, but `get` cannot tell it apart from a missing key; use `remove` to delete.

### Limits

These limits are the same in every browser and do not depend on the browser's own quota.

| Limit             | Value                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| Size of one value | At most 65,536 characters of `JSON.stringify(value)`                                                     |
| Total per plugin  | At most 1,048,576 characters: for every key, the key's length plus the length of `JSON.stringify(value)` |
| Keys per plugin   | At most 1,000                                                                                            |

* A `set` that would exceed a limit rejects with `quota_exceeded` and changes nothing.
* If the browser's own storage is full, `set` also rejects with `quota_exceeded`.
* Limits may be raised within API version 1, but never lowered.

### Storage semantics

* **Atomic writes.** Each `set`, `remove`, or `clear` either happens completely or not at all.
* **Ordered.** Operations from one plugin are applied in the order the plugin made them. A `get` after a `set` sees that `set` if the `set` was requested first, even if the plugin did not await it.
* **No transactions.** There are no transactions across keys or across plugins, so two operations on different keys may be saved separately.
* **Persistent.** Data persists across page reloads, browser restarts, disable/enable, and plugin updates.
* **Deleted on uninstall.** Data is deleted when the plugin is uninstalled. Bundled plugins' data lasts until Venora itself is removed.
* **Local only.** Data is kept in the browser profile. It is not synced between devices, and it is shared by every Fermo account used in that profile. A plugin MUST NOT assume that stored data belongs to the currently signed-in account.
* **Available while running.** Storage can be used while the plugin is `starting` or `running`, but not during cleanup.
* **One writer per tab.** Each open Fermo tab runs its own instance of the plugin, and the ordering guarantee applies within one instance. If instances in two tabs write at the same moment, the plugin's data as saved by one of them wins, and the other tab's change in that moment can be lost. Plugins that may run in several tabs SHOULD NOT depend on writes from different tabs being merged.

### Errors

| Code                     | When                                                                                                                   |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `permission_denied`      | The plugin does not declare `storage`                                                                                  |
| `invalid_payload`        | The key is not a string of 1–100 characters, or the value is not plain data                                            |
| `quota_exceeded`         | A limit would be exceeded; nothing was written                                                                         |
| `capability_failed`      | The browser's storage failed unexpectedly                                                                              |
| `sensitive_data_blocked` | A `get` result contained the session token. That cannot happen in normal use, because plugins never receive the token. |

### Implementation

This describes Venora's internals, not the API. Plugins MUST NOT depend on it.

* **Code:** `venora-chrome/runtime/plugin-storage.js`, behind the `storage.*` capabilities.
* **Separate entry per plugin:** each plugin's data is one extension-storage entry, `venoraPluginStorage:<id>`, keyed by the plugin ID bound to its channel. It is separate from `spacebarPluginSettings` and from Venora's plugin records.
* **Atomic writes:** every operation rewrites only that plugin's entry, so it is all or nothing.
* **Ordering:** each plugin has a queue in its tab. Plugins never wait for each other.
* **Quota checks:** done before anything is written, using the data as it is at that moment.
* **Uninstall:** deletes the entry.
* **Browser storage limit:** Chrome limits extension storage to about 10 MB by default, and imported plugin code is kept there too. Venora does not request the `unlimitedStorage` permission.
  * The 1 MiB per-plugin limit leaves room for Venora's own records in normal use.
  * If the browser's limit is reached anyway, `set` rejects with `quota_exceeded` and nothing is written.
  * Whether to add `unlimitedStorage` remains an open decision (see [plugin-api-status.md](/plugin-api-status.md)).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.getvenora.tech/plugin-api/plugin-settings-storage.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
