---
metadata:
  - name: generator
    content: Diplodoc Platform v5.52.0
  - property: og:type
    content: article
  - property: article:section
    content: Платформа плагинов
  - property: og:title
    content: Plugin data storage
  - property: article:tag
    content: Техническая инструкция
alternate:
  - https://yandex.ru/support/tracker/en/plugins/storage.md
  - https://yandex.ru/support/tracker/ru/plugins/storage.md
  - href: en/plugins/storage.md
    type: text/markdown
    title: Markdown version
  - href: ../llms.txt
    type: text/markdown
    title: llms.txt
---
> **Documentation Index:** Fetch the complete configuration index at https://yandex.ru/support/tracker/en/llms.txt


# Plugin data storage

In addition to interacting with the [Tracker public API](https://yandex.ru/support/tracker/en/plugins/publicApi.md), a plugin has access to its own JSON storage — `storageApi`. It runs on the host side, so the data doesn't depend on the user's device and is available in every tab where the plugin is open.

The storage is useful for plugin user settings, lookup caches, and any data the plugin needs to keep close to the user without saving it in Tracker as an entity.

## Contexts {#contexts}

Each record is stored in a **context** — it defines the data's visibility scope.

Currently, a single context is available:

- **`orgShared`** — data shared across the whole organization. The record is visible to all plugin users in that organization. Write access is granted by the host in the `canWrite` field of each record read.

The list of contexts may expand in the future — for example, a user-level context. The API is designed so that adding a new context won't break existing code.

{% note warning "Shared record" %}

The `orgShared` context is a record **shared** across the whole organization. Don't store the user's personal data in it, and keep in mind that concurrent write processes see each other's changes. For collaborative work, use [versioning](#versioning) and `patch` merge semantics.

{% endnote %}

## Basic usage {#basic-usage}

The `storageApi` object is exported from the SDK:

```typescript
import { storageApi } from '@weavix/sdk-react';
// or
import { storageApi } from '@weavix/sdk-core';

// Reading
const record = await storageApi.orgShared.get('settings');
// record: { data: { theme: 'dark' }, version: 5, canWrite: true, ... } | null

// Creating a record in an empty bucket
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: 'dark', notifications: true },
    version: 0,
});

// Updating without knowing the current version — the SDK reads it itself and retries the request on conflicts
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { count: 42 },
});

// Deleting a single field — pass null
await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: null },
});
```

## Buckets {#buckets}

Within a single context, a plugin can keep several independent records — each identified by the `bucket` parameter. It's just a string key. A record under one bucket doesn't overlap with a record under another.

```typescript
await storageApi.orgShared.patch({ bucket: 'settings', data: { theme: 'dark' } });
await storageApi.orgShared.patch({ bucket: 'cache', data: { fetchedAt: Date.now() } });
```

If `bucket` isn't passed, the host substitutes `'default'`. A key that's invalid in format or length results in a [`BAD_KEY`](#errors) error.

## API {#api}

### storageApi.orgShared.get(bucket?) {#storage-api-org-shared-get}

Returns the current record for the bucket, or `null` if the record doesn't exist yet.

**Parameters:**

| Parameter | Type                   | Default                       | Description                     |
| -------- | --------------------- | ----------------------------- | ---------------------------- |
| `bucket` | `string | undefined` | the host substitutes `'default'` | The record's key within the context |

**Returns:** `Promise<StorageRecord | null>`

```typescript
const record = await storageApi.orgShared.get('settings');

if (record) {
    console.log(record.data); // record contents
    console.log(record.version); // current version (for optimistic updates)
    console.log(record.canWrite); // whether the current user has write access
} else {
    // the record doesn't exist yet — it can be created via patch with version: 0
}
```

### storageApi.orgShared.patch(options) {#storage-api-org-shared-patch}

Merge patch: the fields from `data` are applied on top of the current record, and a `null` value removes the key. Returns the full merged `StorageRecord` with the new version — including fields written by concurrent write processes.

**Parameters:**

| Parameter  | Type                       | Default                 | Description                       |
| --------- | ------------------------- | ---------------------------- | ------------------------------ |
| `bucket`  | `string`                  | the host substitutes `'default'` | The record's key                    |
| `data`    | `Record<string, unknown>` | —                            | Merge doc; `null` removes a field |
| `version` | `number | undefined`     | auto-resolve                 | Expected current version       |

**Returns:** `Promise<StorageRecord>` — the full record after the patch is applied.

```typescript
const updated = await storageApi.orgShared.patch({
    bucket: 'settings',
    data: { theme: 'dark' },
});

updated.version; // version after the patch
updated.data; // full record contents (including other clients' fields)
```

## Versioning {#versioning}

The storage works on an optimistic-update scheme: every record has a `version`, and `patch` must pass the expected version. If the host already has a newer version, the operation is rejected with [`VERSION_CONFLICT`](#errors).

The `patch` behavior depends on whether you pass `version`:

- **With an explicit `version`** — a single request is sent. Any error, including `VERSION_CONFLICT`, is thrown to the caller. Use this mode if the application itself keeps track of the current version and needs to react to a conflict — for example, to show the user "the data has changed, please reload." To create a record in an empty bucket, pass `version: 0`.

- **Without `version`** — the SDK first reads the current version via `get` (for an empty bucket, `0` is used), then performs the `patch`. On `VERSION_CONFLICT` the cycle repeats up to twice, re-reading the version on each retry. Any other error is thrown immediately. In the worst case, this results in 6 requests to the host (3 GET + PATCH pairs). Each retry logs a `console.warn`.

{% note info "Which mode to choose" %}

- Pass `version` explicitly if you've loaded the record onto the screen and the user is editing it — this way a conflict with another writer won't be silently lost.
- Don't pass `version` if the change doesn't depend on the previous state — for example, incrementing a counter or appending a new field.

{% endnote %}

## Write access {#can-write}

The `canWrite` field in `StorageRecord` shows whether the current user can `patch` this record. Use it to show the user in advance that they don't have permission, without waiting for an error from the host.

```typescript
const record = await storageApi.orgShared.get('settings');

if (!record?.canWrite) {
    // hide or disable editing elements
}
```

## Error codes {#errors}

All `storageApi` errors are instances of `PluginActionError` with numeric codes. The constants are exported from the SDK:

```typescript
import {
    VERSION_CONFLICT,
    DATA_TOO_LARGE,
    BAD_KEY,
} from '@weavix/sdk-react';
```

| Constant          | Code    | When                                                       |
| ------------------ | ------ | ----------------------------------------------------------- |
| `VERSION_CONFLICT` | `1010` | The passed `version` doesn't match the current one          |
| `DATA_TOO_LARGE`   | `1011` | The record size after the operation exceeded 256 KiB        |
| `BAD_KEY`          | `1012` | The `bucket` failed format or length validation              |

General API error codes (validation, scope, and so on) are in the [API error codes](https://yandex.ru/support/tracker/en/plugins/publicApi.md#codeErrors) section.

**Example of handling `VERSION_CONFLICT`:**

```typescript
import {
    storageApi,
    PluginActionError,
    VERSION_CONFLICT,
} from '@weavix/sdk-react';

try {
    await storageApi.orgShared.patch({
        bucket: 'settings',
        data: { theme: 'dark' },
        version: knownVersion,
    });
} catch (e) {
    if (e instanceof PluginActionError && e.code === VERSION_CONFLICT) {
        // the data has changed — reload the record and offer the user to merge the changes
        const fresh = await storageApi.orgShared.get('settings');
        // ...
    } else {
        throw e;
    }
}
```

## Types {#types}

```typescript
import type {
    StorageRecord,
    StorageContextType,
    StorageGetPayload,
    StoragePatchPayload,
} from '@weavix/sdk-react';
```

### StorageRecord {#storage-record}

A storage record.

```typescript
type StorageRecord = {
    /** Internal composite record key (context + bucket). */
    key: Array<{ organization_shared: string }>;
    /** Record version. Pass it to patch for optimistic updates. */
    version: number;
    /** The record's payload data. */
    data: Record<string, unknown>;
    /** Whether the current user can patch. */
    canWrite: boolean;
    /** Record creation date (ISO 8601). */
    createdAt: string;
    /** Date of the last update (ISO 8601). */
    updatedAt: string;
};
```

### StorageContextType {#storage-context-type}

```typescript
type StorageContextType = 'orgShared';
```

## Limits {#limits}

- The maximum record size is **256 KiB** after the operation is applied. Exceeding it results in `DATA_TOO_LARGE`.
- The bucket key is validated by the host for format and length. An invalid key results in `BAD_KEY`.
- `patch` without an explicit `version` makes up to 2 retries on `VERSION_CONFLICT`. After that, the error is thrown to the caller.
