Every config operation crosses the JavaScript–Rust bridge via Tauri’s IPC layer. When your app manages several config files at once — for example a user.json, a workspace.json, and a secrets.json — calling load() on each one separately means multiple round-trips. The batch API collapses all of that into a single IPC call per operation type.
When to use batch operations
Use batch operations when you need to read or write several configs at startup or in response to the same user action. The benefits are:
- Single IPC round-trip — one Tauri invoke instead of N.
- Atomic submission — all entries are sent together, so the Rust side processes them in one pass.
- Per-entry fault isolation — a failure in one entry does not abort the others; each result is independent.
All three batch methods (loadAll, saveAll, patchAll) cross the JavaScript–Rust bridge exactly once per call, regardless of how many entries are in the batch. This makes them significantly more efficient than calling the single-config API in a loop.
For a single config, the standard config.load() / config.save() / config.patch() API is simpler and preferred.
loadAll
Configurate.loadAll(entries) loads multiple configs in a single IPC call. Each entry requires an id (unique within the batch) and the config instance.
import { Configurate } from "tauri-plugin-configurate-api";
const result = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "user", config: userConfig },
{ id: "secret", config: secretConfig },
]).run();
Unlocking specific entries
Chain .unlock(id, keyringOpts) to fill keyring fields for a particular entry:
const result = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "secret", config: secretConfig },
])
.unlock("secret", { service: "my-app", account: "default" })
.run();
Unlocking all entries at once
Use .unlockAll(keyringOpts) when every config in the batch uses the same keyring credentials:
const result = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "secret", config: secretConfig },
])
.unlockAll({ service: "my-app", account: "default" })
.run();
Per-ID .unlock() takes precedence over .unlockAll(). If you chain both, the entry-specific credentials are used for entries that match an ID, and .unlockAll() credentials are used for the rest.
saveAll
Configurate.saveAll(entries) saves multiple configs in a single IPC call. Each entry requires an id, the config instance, and the full data to write.
const result = await Configurate.saveAll([
{ id: "app", config: appConfig, data: { theme: "dark", language: "en" } },
{ id: "secret", config: secretConfig, data: { token: "tok-abc" } },
])
.lock("secret", { service: "my-app", account: "default" })
.run();
Locking all entries at once
const result = await Configurate.saveAll([
{ id: "app", config: appConfig, data: appData },
{ id: "secret", config: secretConfig, data: secretData },
])
.lockAll({ service: "my-app", account: "default" })
.run();
patchAll
Configurate.patchAll(entries) deep-merges partial data into multiple configs in a single IPC call. Only the keys you provide are updated; all other keys remain as stored.
const result = await Configurate.patchAll([
{ id: "app", config: appConfig, data: { theme: "light" } },
{ id: "user", config: userConfig, data: { language: "ja" } },
]).run();
Patch entries with keyring fields also accept .lock() and .lockAll():
const result = await Configurate.patchAll([
{ id: "secret", config: secretConfig, data: { token: "rotated-token" } },
])
.lock("secret", { service: "my-app", account: "default" })
.run();
Handling BatchRunResult
All three batch methods return a Promise<BatchRunResult>. The results property is a record keyed by the IDs you provided. Each value is either a success or a per-entry failure.
interface BatchRunResult {
results: Record<string, BatchRunEntryResult>;
}
type BatchRunEntryResult =
| { ok: true; data: unknown }
| { ok: false; error: { kind: string; message: string } };
Check .ok before accessing .data:
const result = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "secret", config: secretConfig },
]).run();
if (result.results.app.ok) {
console.log(result.results.app.data);
} else {
console.error("app load failed:", result.results.app.error.message);
}
if (result.results.secret.ok) {
console.log(result.results.secret.data);
}
A failure in one batch entry does not abort the others. Always check .ok for every entry — do not assume all entries succeeded because the await resolved.
Iterating all results
for (const [id, entry] of Object.entries(result.results)) {
if (entry.ok) {
console.log(`${id}: loaded`, entry.data);
} else {
console.error(`${id}: failed — ${entry.error.kind}: ${entry.error.message}`);
}
}
Full example: app startup load
The following pattern loads multiple configs at startup, unlocks the one containing secrets, and falls back gracefully if any entry is missing.
import { Configurate } from "tauri-plugin-configurate-api";
import { appConfig, userConfig, secretConfig } from "./configs";
const KEYRING = { service: "my-app", account: "default" };
export async function loadAllConfigs() {
const result = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "user", config: userConfig },
{ id: "secret", config: secretConfig },
])
.unlock("secret", KEYRING)
.run();
const app = result.results.app;
const user = result.results.user;
const secret = result.results.secret;
if (!app.ok) throw new Error(`Failed to load app config: ${app.error.message}`);
if (!user.ok) throw new Error(`Failed to load user config: ${user.error.message}`);
if (!secret.ok) throw new Error(`Failed to load secret config: ${secret.error.message}`);
return {
app: app.data,
user: user.data,
secret: secret.data,
};
}