The FDO SDK provides two built-in storage backends accessed through PluginRegistry.useStore(). Both are scoped automatically to your plugin’s identity so you don’t need to namespace keys yourself at the store level — though namespacing individual keys within a store is still a good practice.
Store types
| Type | Key | Persistent | Requires capability |
|---|
| In-memory | "default" | No — data lost on restart | No |
| JSON file | "json" | Yes — survives restarts | storage.json |
StoreType interface
Both stores implement the same StoreType interface:
interface StoreType {
get<T = unknown>(key: string): T | undefined;
set<T = unknown>(key: string, value: T): void;
remove(key: string): void;
clear(): void;
has(key: string): boolean;
keys(): string[];
}
Stores support any JSON-serializable value: strings, numbers, booleans, objects, and arrays.
In-memory store (default)
PluginRegistry.useStore("default") returns an in-memory store. Data exists only for the lifetime of the current session and is cleared when the application restarts.
import { PluginRegistry } from "@anikitenko/fdo-sdk";
const store = PluginRegistry.useStore("default");
store.set("visitCount", 1);
store.get("visitCount"); // → 1
store.has("visitCount"); // → true
store.keys(); // → ["visitCount"]
store.remove("visitCount");
store.clear();
Use the default store for session data: counters, temporary state, or anything that does not need to survive a restart.
JSON file store (persistent)
PluginRegistry.useStore("json") returns a file-backed store. Data is written to a store.json file under your plugin’s storage directory and survives application restarts.
const store = PluginRegistry.useStore("json");
store.set("userName", "Alice");
store.set("theme", "dark");
store.get("userName"); // → "Alice"
The JSON store writes asynchronously in a queued manner so that concurrent set() calls do not corrupt the file. The store uses atomic writes to protect against data loss on crash.
Configuring the storage root
The JSON store requires a storage root directory configured before use. Without it, calling PluginRegistry.useStore("json") throws:
JSON store requires a configured storage root.
Set PluginRegistry.configureStorage({ rootDir }) or FDO_SDK_STORAGE_ROOT.
Configure the root in one of two ways:
Set FDO_SDK_STORAGE_ROOT before your plugin process starts:FDO_SDK_STORAGE_ROOT=/var/fdo/storage node plugin.js
Call configureStorage() before calling useStore("json"):import { PluginRegistry } from "@anikitenko/fdo-sdk";
PluginRegistry.configureStorage({ rootDir: "/var/fdo/storage" });
const store = PluginRegistry.useStore("json");
In practice, the FDO host calls this for you during plugin initialization when it grants the storage.json capability.
The JSON store also requires the storage.json capability to be granted by the FDO host. If your plugin calls PluginRegistry.useStore("json") without that capability, the SDK throws a permission error even if a storage root is configured. Request this capability from your FDO host administrator.
Storage scoping
Stores are scoped per plugin. The SDK derives the scope from your plugin’s metadata.author and metadata.name, or from metadata.id if you set it explicitly. You don’t need to do anything — the store instance you get from useStore() is already isolated to your plugin.
JSON files are stored at:
<storageRoot>/<pluginId>/store.json
Where <pluginId> is derived from your metadata.
Set metadata.id to a stable, unique string (e.g. "mycompany.myplugin") so your storage path doesn’t change if you rename the plugin’s display name.
Key naming
Even though stores are scoped per plugin, it’s good practice to namespace your keys to keep them readable and organized:
const KEYS = {
USER_NAME: "prefs:userName",
USER_THEME: "prefs:theme",
VISIT_COUNT: "session:visitCount",
};
Complete example
The following is based on examples/03-persistence-plugin.ts and shows both stores used together:
import {
FDO_SDK,
FDOInterface,
PluginMetadata,
PluginRegistry,
} from "@anikitenko/fdo-sdk";
export default class PersistencePlugin extends FDO_SDK implements FDOInterface {
private readonly _metadata: PluginMetadata = {
name: "Persistence Plugin Example",
version: "1.0.0",
author: "FDO SDK Team",
description: "Demonstrates data persistence with StoreDefault and StoreJson",
icon: "database"
};
private readonly KEYS = {
USER_NAME: "prefs:userName",
USER_THEME: "prefs:theme",
VISIT_COUNT: "session:visitCount",
LAST_ACTION: "session:lastAction",
};
get metadata(): PluginMetadata {
return this._metadata;
}
init(): void {
try {
this.log("PersistencePlugin initialized!");
// In-memory store — always available
const tempStore = PluginRegistry.useStore("default");
// Persistent store — falls back to in-memory if JSON store unavailable
let persistentStore: ReturnType<typeof PluginRegistry.useStore>;
try {
persistentStore = PluginRegistry.useStore("json");
} catch (error) {
this.warn("JSON store not available, using in-memory fallback");
persistentStore = tempStore;
}
// Track visits in session
const visits = (tempStore.get<number>(this.KEYS.VISIT_COUNT) ?? 0) + 1;
tempStore.set(this.KEYS.VISIT_COUNT, visits);
this.info(`Visit count this session: ${visits}`);
PluginRegistry.registerHandler("savePreferences", (data: any) => {
if (data.userName !== undefined) {
persistentStore.set(this.KEYS.USER_NAME, data.userName);
}
if (data.theme !== undefined) {
persistentStore.set(this.KEYS.USER_THEME, data.theme);
}
return { success: true, message: "Preferences saved" };
});
PluginRegistry.registerHandler("clearPreferences", () => {
persistentStore.remove(this.KEYS.USER_NAME);
persistentStore.remove(this.KEYS.USER_THEME);
return { success: true, message: "Preferences cleared" };
});
PluginRegistry.registerHandler("recordAction", (data: any) => {
tempStore.set(this.KEYS.LAST_ACTION, {
action: data.action ?? "unknown",
timestamp: new Date().toISOString(),
});
return { success: true };
});
} catch (error) {
this.error(error as Error);
}
}
render(): string {
return `<div style="padding: 20px;">Plugin UI here</div>`;
}
}
new PersistencePlugin();
Error handling for storage
Wrap storage calls in try-catch blocks — the JSON store can fail due to disk errors or missing capabilities:
try {
const store = PluginRegistry.useStore("json");
store.set("key", "value");
} catch (error) {
this.error(error as Error);
// Fall back to in-memory store or degrade gracefully
}