Working with Data
Learn how to set, read, update, and remove persistent data using the Tauri Store Plugin in a React and Vite application
The Store Plugin persists key-value data to disk as JSON. Every value you store must be serializable — numbers, strings, booleans, arrays, and plain objects all work. Functions, class instances, and cyclic structures will throw an error. Create the store first with Creating a Store if you have not loaded one yet.
Operations on a store are asynchronous and return Promises. That means you always await them or chain .then().
Setting Values
Use store.set(key, value) to write a key. The plugin serializes the value to JSON and schedules a disk write. By default, writes are debounced — multiple rapid set calls within a short window get batched into one file write. The default debounce for a Store is 100ms.
import { Store } from "@tauri-apps/plugin-store";
async function saveSettings() {
const store = new Store("settings.json");
await store.set("theme", "dark");
await store.set("volume", 0.8);
await store.set("preferences", {
notifications: true,
fontSize: 14,
});
}
The value passed to set replaces whatever was previously stored under that key. There is no deep merge — if you want to update a single field inside an object, you must read the entire object, modify it, and write it back.
Only JSON-serializable values:
Passing a Date, Map, Set, or object with circular references to set will cause a serialization error at runtime. If you need to store dates, convert them to ISO strings or timestamps first.
After calling set, the data is not yet guaranteed to be on disk. The plugin writes in the background. If you need to be certain the data is persisted before continuing — for example, before the app exits — use store.save() immediately after the last set.
Verify the write:
Read the value back with store.get("theme") right after setting it. If the value matches, your store instance is working correctly and the in-memory state is accurate.
Reading Values
store.get(key) returns a Promise that resolves to the stored value, or undefined if the key does not exist. You can provide a type parameter to tell TypeScript what shape you expect.
const theme = await store.get<string>("theme");
// theme is string | undefined
interface Preferences {
notifications: boolean;
fontSize: number;
}
const prefs = await store.get<Preferences>("preferences");
A missing key silently returns undefined. If your application requires a default value when no stored value exists, handle that explicitly:
const volume = (await store.get<number>("volume")) ?? 0.5;
Load from disk explicitly:
A store instance holds data in memory. If another part of your application, or another process, modifies the JSON file on disk, your in-memory state will be stale. Call store.load() to reload all data from disk into the store instance.
Updating Values
Updating a value is the same operation as setting it. Call store.set again with the same key and the new value.
await store.set("volume", 0.9);
For objects, you must retrieve the full value first, modify the field you care about, and write the whole object back. There is no partial update method.
const prefs = await store.get<Preferences>("preferences");
if (prefs) {
prefs.fontSize = 16;
await store.set("preferences", prefs);
}
A common mistake is to assume set merges fields. It always replaces the entire value at that key.
No atomic partial update:
If two parts of your application read-modify-write the same object concurrently, you can lose data because each write overwrites the entire key. The store plugin does not provide locks. For single-writer scenarios (most desktop apps) this is rarely a problem, but be aware of it if you have multiple windows writing to the same key.
Removing Values
Call store.delete(key) to remove a single key. It returns a Promise that resolves to true if the key existed and was removed, false if the key was not present.
const wasRemoved = await store.delete("temporaryToken");
To remove every key at once, use store.clear().
await store.clear();
Both operations are persistent — the file on disk will reflect the deletions after the next debounced write or after an explicit save().
Forcing Save and Reload
Beyond the automatic debounced saves, you have two manual controls over persistence.
store.save()— immediately writes all in-memory state to the JSON file on disk. Use this before the app quits or after a batch of critical writes.store.load()— re-reads the JSON file from disk into memory, discarding any in-memory changes that haven't been saved yet.
// After several sets, force a write now
await store.set("lastBackup", Date.now());
await store.save();
// Reload after external modification
await store.load();
const freshBackup = await store.get<number>("lastBackup");
In most applications you won't call load frequently, but it's essential if your app can be sideloaded with updated config files while running.
Watching for Changes
A store instance can notify you whenever a key changes. The onChange method accepts a callback that receives the key and its new value. It returns a function to stop listening.
import { useEffect, useState } from "react";
import { Store } from "@tauri-apps/plugin-store";
function useStoreListener(store: Store | null) {
const [theme, setTheme] = useState<string>("light");
useEffect(() => {
if (!store) return;
let unlisten: (() => void) | undefined;
const setupListener = async () => {
unlisten = await store.onChange((key, value) => {
if (key === "theme" && typeof value === "string") {
setTheme(value);
}
});
};
setupListener();
return () => {
if (unlisten) unlisten();
};
}, [store]);
return theme;
}
This is useful when the same store is mutated in multiple places — for example, from a settings page and from a tray menu command — and the UI needs to stay in sync without polling.
Error Handling
Store operations can fail for file-system reasons: the disk may be full, permissions may be missing, or the JSON file may be corrupted. Always wrap store calls in try/catch in production code.
try {
await store.set("key", value);
} catch (error) {
console.error("Failed to write to store:", error);
// Show a user-friendly message or fallback
}
A corrupted JSON file will cause load() to throw. In that case, creating a fresh store (which overwrites the file on the first set) is a simple recovery strategy.
LazyStore and crash safety:
LazyStore debounces writes and uses an in-memory buffer. During an unclean shutdown — power loss, OS crash — the file on disk can become corrupted (empty or filled with null bytes). If your application must survive hard crashes, prefer the regular Store with explicit save() calls at safe checkpoints, or implement a write-to-temp-then-rename strategy yourself.
Putting It Together in a React Component
Here is a complete component that creates a store, reads an existing value, lets the user change it, and saves on demand. It demonstrates setting, reading, and manual saving.
import { useState, useEffect } from "react";
import { Store } from "@tauri-apps/plugin-store";
function Settings() {
const [store, setStore] = useState<Store | null>(null);
const [volume, setVolume] = useState(0.5);
useEffect(() => {
const init = async () => {
const s = new Store("settings.json");
const saved = await s.get<number>("volume");
if (saved !== undefined) setVolume(saved);
setStore(s);
};
init();
}, []);
const handleVolumeChange = async (newVolume: number) => {
if (!store) return;
setVolume(newVolume);
await store.set("volume", newVolume);
};
const handleSaveNow = async () => {
if (!store) return;
await store.save();
alert("Saved to disk.");
};
return (
<div>
<label>
Volume
<input
type="range"
min={0}
max={1}
step={0.05}
value={volume}
onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
/>
</label>
<span>{Math.round(volume * 100)}%</span>
<button onClick={handleSaveNow}>Force Save</button>
</div>
);
}
export default Settings;
Startup defaults:
The component reads the stored volume on mount and falls back to 0.5 if nothing is saved yet. This pattern — read first, provide a sensible default — avoids forcing the user to reconfigure after every fresh install.
Summary
The Store Plugin gives you persistent key-value storage with a straightforward API. Data is always JSON, operations are always asynchronous, and you control when writes hit disk. Use set and get for basic reads and writes, delete and clear for removal, and save to guarantee durability. Listen for changes with onChange to keep your UI reactive.
The most durable pattern is: read once at startup, mutate in memory during the session, and write back explicitly before the app closes.