Skip to main content
Every configuration file managed by tauri-plugin-configurate goes through the same lifecycle: you create it once, load it on subsequent launches, save changes when the user updates settings, and delete it when you no longer need it. This guide walks you through each operation using a realistic schema that includes both a plain field and a keyring-protected secret, so you can see exactly how secrets are handled at each step.

Setup

The examples throughout this guide share a single Configurate instance. Define your schema and construct the instance once — typically in a dedicated module that you import wherever you need config access.
import {
  BaseDirectory,
  Configurate,
  JsonProvider,
  defineConfig,
  keyring,
} from "tauri-plugin-configurate-api";

const schema = defineConfig({
  theme: String,
  database: {
    host: String,
    password: keyring(String, { id: "db-password" }),
  },
});

const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
});

const KEYRING = { service: "my-app", account: "default" };
Pass a defaults object to the Configurate constructor to automatically fill in missing keys whenever you call load(). This is useful for gracefully adding new fields to an existing config file without running a full migration:
const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  defaults: { theme: "system" },
});

create(data)

config.create(data) writes a brand-new config file to disk. It returns a LazyConfigEntry that you finish by chaining one of three terminal methods before awaiting.
Terminal methodDescriptionReturns
.lock(keyringOpts).run()Stores keyring secrets, returns locked dataPromise<LockedConfig<S>>
.unlock(keyringOpts)Stores keyring secrets, returns unlocked dataPromise<UnlockedConfig<S>>
.run()Runs without keyring (schemas with no keyring() fields only)Promise<LockedConfig<S>>
// Store secrets and get back a LockedConfig (password is null)
const locked = await config
  .create({ theme: "dark", database: { host: "localhost", password: "secret" } })
  .lock(KEYRING)
  .run();

// Store secrets and get back an UnlockedConfig (password is populated)
const unlocked = await config
  .create({ theme: "dark", database: { host: "localhost", password: "secret" } })
  .unlock(KEYRING);

console.log(unlocked.data.database.password); // "secret"

load()

config.load() reads an existing config file from disk. Keyring-protected fields are returned as null in the locked form; call .unlock() to populate them from the OS keyring.
// Locked load — password is null
const locked = await config.load().run();
console.log(locked.data.database.password); // null

// Unlocked load — password is fetched from the keyring
const unlocked = await config.load().unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"
If you have already loaded a LockedConfig and later decide you need the secrets, you can unlock it without loading from disk again:
const locked = await config.load().run();

// Later in the same session...
const unlocked = await locked.unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"
1

Load the config

Call config.load().run() to read the file from disk. This is fast and does not touch the OS keyring.
2

Unlock when you need secrets

Call locked.unlock(KEYRING) (or config.load().unlock(KEYRING) directly) only when your code actually needs the secret values. Minimising time that plaintext secrets live in memory is good practice.
3

Lock when done

Call unlocked.lock() to revoke access to the decrypted data through the current UnlockedConfig instance.

save(data)

config.save(data) completely overwrites an existing config file with the data you provide. Every field — including keyring-protected ones — is replaced.
await config
  .save({ theme: "light", database: { host: "db.example.com", password: "new-secret" } })
  .lock(KEYRING)
  .run();
The same terminal methods available on create() work here: .lock(opts).run(), .unlock(opts), and .run() (for keyring-free schemas).

delete(keyringOpts?)

config.delete() removes the config file from disk. When you pass KeyringOptions, it also purges all associated keyring entries for that config so no orphaned secrets are left behind.
// Delete file and its keyring entries
await config.delete(KEYRING);

// Delete file only (schema has no keyring fields)
await config.delete();

exists()

config.exists() returns a boolean indicating whether the config file is present on disk. Use this to decide whether to call create() or load() on first launch.
const present: boolean = await config.exists();

if (!present) {
  await config
    .create({ theme: "system", database: { host: "localhost", password: "" } })
    .lock(KEYRING)
    .run();
} else {
  const locked = await config.load().run();
  console.log(locked.data.theme);
}

list()

config.list() scans the root directory and returns the file names of all configs stored by the same provider. Backup files and temporary files are excluded automatically.
const files: string[] = await config.list();
// ["app.json", "user.json"]
This is useful when your app manages multiple named config files (for example, per-user profiles) and you need to enumerate them at runtime.

reset(data)

config.reset(data) is a convenience operation that deletes the existing config and immediately re-creates it with the data you supply — all in a single IPC call. It is equivalent to delete() followed by create(), but atomic from the plugin’s perspective.
const locked = await config
  .reset({ theme: "dark", database: { host: "localhost", password: "secret" } })
  .lock(KEYRING)
  .run();
Like create() and save(), reset() supports .lock(opts).run(), .unlock(opts), and .run() as terminal methods.