Skip to main content

Storage API

Persistent, extension-scoped storage — key-value pairs and private directories.

Overview

The Storage API provides two storage mechanisms under a single storage namespace:

  • storage.kv — A key-value store (like localStorage). Keys and values are plain strings, scoped per extension. Data persists across app restarts.
  • storage.getDataDir() / storage.getCacheDir() — Private filesystem directories for storing files of any size.

No permissions are required. Both are available to all extensions by default.

Key-Value Storage (storage.kv)

storage.kv.setItem

Store a string value under a key. If the key already exists, the value is updated in place (upsert). Creating a new key counts against the per-extension key limit; updating an existing key does not.

ParameterTypeRequiredDescription
keystringYesThe key to store the value under. Max 256 characters.
valuestringYesThe string value to store. Max 4096 characters (~4 KB).

Returns: Promise<void>

Errors:

ConditionError message
Key exceeds 256 charactersStorage key too long (max 256 characters)
Value exceeds 4096 charactersStorage value too large (max 4096 characters)
Extension already has 1000 keys and this is a new keyStorage quota exceeded (max 1000 keys per extension)
import editor from "@srcnexus/ext-sdk";

// Simple string
await editor.storage.kv.setItem("theme", "dark");

// Store a complex value by serializing to JSON
await editor.storage.kv.setItem("settings", JSON.stringify({
fontSize: 14,
wordWrap: true,
language: "en",
}));

storage.kv.getItem

Retrieve the value stored under a key.

ParameterTypeRequiredDescription
keystringYesThe key to look up.

Returns: Promise<string | null> -- the stored value, or null if the key does not exist.

const theme = await editor.storage.kv.getItem("theme");
if (theme !== null) {
console.log("Stored theme:", theme);
} else {
console.log("No theme saved yet");
}

// Retrieve and parse a JSON value
const raw = await editor.storage.kv.getItem("settings");
if (raw !== null) {
const settings = JSON.parse(raw);
console.log("Font size:", settings.fontSize);
}

storage.kv.removeItem

Delete a single key-value pair. If the key does not exist, this is a no-op.

ParameterTypeRequiredDescription
keystringYesThe key to remove.

Returns: Promise<void>

await editor.storage.kv.removeItem("theme");

storage.kv.clear

Remove all key-value pairs stored by the calling extension. Other extensions' data is not affected.

ParameterTypeRequiredDescription
(none)------

Returns: Promise<void>

// Wipe all stored data for this extension
await editor.storage.kv.clear();

storage.kv.getAll

Retrieve every key-value pair stored by the calling extension as a single object.

ParameterTypeRequiredDescription
(none)------

Returns: Promise<Record<string, string>> -- an object mapping every stored key to its value. Returns an empty object {} if nothing has been stored.

const allData = await editor.storage.kv.getAll();
console.log(allData);
// { theme: "dark", settings: "{\"fontSize\":14,\"wordWrap\":true}" }

Limits and Quotas

The key-value storage enforces hard limits to prevent any single extension from consuming excessive resources:

LimitValueNotes
Max key length256 charactersEnforced per call to setItem.
Max value size4,096 charactersApproximately 4 KB per value.
Max keys per extension1,000Only new keys count; updating an existing key is always allowed.

Notes:

  • Updating an existing key does not count against the 1,000-key limit and will always succeed, as long as the key and value lengths are within bounds.
  • If you need to store values larger than 4 KB, split them across multiple keys or consider compressing the data before storing.

Directory Storage (storage.getDataDir / storage.getCacheDir)

For files of any size — binary data, large JSON, assets, databases — use the directory methods.

storage.getDataDir

Get the extension's private data directory (persistent). Created on first call. Only deleted when the extension is uninstalled.

ParameterTypeRequiredDescription
(none)------

Returns: Promise<string> — the file:// URI of the data directory.

import editor from "@srcnexus/ext-sdk";

const dataDir = await editor.storage.getDataDir();
console.log(dataDir);
// file:///data/data/<package>/app_flutter/extension_data/my-extension/

storage.getCacheDir

Get the extension's private cache directory. Intended for temporary or re-creatable data. The system may clear cache files when storage is low.

ParameterTypeRequiredDescription
(none)------

Returns: Promise<string> — the file:// URI of the cache directory.

import editor from "@srcnexus/ext-sdk";

const cacheDir = await editor.storage.getCacheDir();
console.log(cacheDir);
// file:///data/data/<package>/cache/extension_cache/my-extension/

Working with Files

The directory methods only return paths. Use the File System API (workspace.fs.*) to read, write, create, or delete files. Extensions have implicit access to their own directories — no fileSystem permission required.

import editor from "@srcnexus/ext-sdk";

const dataDir = await editor.storage.getDataDir();

// Create a file
const fileUri = await editor.workspace.fs.create(dataDir, "config.json");

// Write to it
await editor.workspace.fs.write(fileUri, JSON.stringify({ theme: "dark" }));

// Read it back
const content = await editor.workspace.fs.read(fileUri);
const config = JSON.parse(content);

// List directory contents
const files = await editor.workspace.fs.list(dataDir);

// Delete a file
await editor.workspace.fs.delete_(fileUri);

Data vs Cache — When to Use Which

Data DirectoryCache Directory
PurposeImportant, persistent dataTemporary, re-creatable data
Survives restartsYesYes (but system may clear)
Deleted on uninstallYesYes
Use forUser preferences files, downloaded assets, databasesHTTP response caches, temporary processing files

Data Isolation

Each extension's storage (both key-value and directories) is fully isolated:

  • Storage is scoped by the extension's unique extensionId. An extension can only access its own data.
  • The extensionId is injected automatically by the extension host when routing RPC calls -- extensions cannot forge or spoof another extension's ID.
  • There is no cross-extension storage access. Extensions that need to share data should use the Messaging API instead.

Cleanup and Lifecycle

  • Uninstall: When an extension is uninstalled, all storage (key-value entries and directories) is automatically deleted.
  • Manual clear: Call storage.kv.clear() to remove all key-value entries. Use workspace.fs.* to clean up directory contents.
  • Removing individual keys: Use storage.kv.removeItem(key) to delete a specific entry and free up a slot in the 1,000-key quota.
  • Disable/Enable: Disabling an extension does not delete its storage. Data persists across enable/disable cycles.
  • Update: Updating an extension preserves all storage.

Choosing the Right Storage

storage.kv (key-value)storage.getDataDir (directories)
Data modelString key-value pairsFiles and directories
Size limits4 KB per value, 1000 keysNo hard limit
Best forSmall settings, preferences, tokensBinary files, large data, assets
Accessstorage.kv.setItem() / getItem()storage.getDataDir() + workspace.fs.*
PermissionNoneNone

Examples

Persisting user preferences

import editor from "@srcnexus/ext-sdk";

const PREFS_KEY = "userPreferences";

const DEFAULT_PREFS = {
autoFormat: true,
indentSize: 2,
showLineNumbers: true,
};

async function loadPreferences() {
const raw = await editor.storage.kv.getItem(PREFS_KEY);
if (raw === null) {
return { ...DEFAULT_PREFS };
}
return { ...DEFAULT_PREFS, ...JSON.parse(raw) };
}

async function savePreferences(prefs) {
await editor.storage.kv.setItem(PREFS_KEY, JSON.stringify(prefs));
}

// Usage
const prefs = await loadPreferences();
prefs.indentSize = 4;
await savePreferences(prefs);

Caching network responses

import editor from "@srcnexus/ext-sdk";

const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour

async function getCachedOrFetch(cacheKey, url) {
const cached = await editor.storage.kv.getItem(cacheKey);
if (cached !== null) {
const { data, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp < CACHE_TTL_MS) {
return data;
}
}

const response = await editor.network.fetch(url);
const data = response.body;

// Only cache if it fits within the 4 KB value limit
const serialized = JSON.stringify({ data, timestamp: Date.now() });
if (serialized.length <= 4096) {
await editor.storage.kv.setItem(cacheKey, serialized);
}

return data;
}

Caching downloaded assets (directory storage)

import editor from "@srcnexus/ext-sdk";

async function getCachedAsset(filename, downloadUrl) {
const cacheDir = await editor.storage.getCacheDir();
const files = await editor.workspace.fs.list(cacheDir);
const cached = files.find((f) => f.name === filename);

if (cached) {
return await editor.workspace.fs.read(cached.uri);
}

// Download and cache
const response = await editor.network.fetch(downloadUrl);
const fileUri = await editor.workspace.fs.create(cacheDir, filename);
await editor.workspace.fs.write(fileUri, response.body);
return response.body;
}

Managing a list of items

import editor from "@srcnexus/ext-sdk";

const LIST_KEY = "recentFiles";

async function addRecentFile(filePath) {
const raw = await editor.storage.kv.getItem(LIST_KEY);
const list = raw !== null ? JSON.parse(raw) : [];

// Remove duplicates, add to front, keep last 20
const updated = [filePath, ...list.filter((f) => f !== filePath)].slice(0, 20);

await editor.storage.kv.setItem(LIST_KEY, JSON.stringify(updated));
}

async function getRecentFiles() {
const raw = await editor.storage.kv.getItem(LIST_KEY);
return raw !== null ? JSON.parse(raw) : [];
}

async function clearRecentFiles() {
await editor.storage.kv.removeItem(LIST_KEY);
}