Skip to main content
In this guide you will build a fully working configuration setup for a Tauri v2 desktop app. You will define a typed schema that covers UI preferences and a nested settings object, persist it as a JSON file in the OS app-config directory, and protect a sensitive field in the native OS keyring — so it never touches disk in plain text. By the end you will know how to create, load, save, patch, and delete config entries using tauri-plugin-configurate.
1
Install the plugin
2
Add the Rust crate to src-tauri/Cargo.toml:
3
[dependencies]
tauri-plugin-configurate = "0.5.0"
4
Register it in src-tauri/src/lib.rs:
5
fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_configurate::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
6
Then install the JavaScript bindings with your package manager:
7
pnpm add tauri-plugin-configurate-api
# or: npm add / yarn add / bun add
8
Finally, grant the required permissions in your capability file (e.g. src-tauri/capabilities/default.json):
9
{
  "permissions": ["configurate:default", "configurate:allow-unlock"]
}
10
configurate:allow-unlock is required whenever you call .unlock() on a load, create, or save operation. If your schema has no keyring fields you can omit it.
11
Define your schema
12
Use defineConfig() to declare the shape of your configuration. Each field maps to a TypeScript primitive constructor (String, Number, Boolean), a nested object, an array, or a special marker:
13
  • keyring(typeCtor, opts) — stores the field in the OS keyring instead of on disk. Requires a unique id string.
  • optional(schema) — marks a field as optional (may be absent from stored config without failing validation).
  • 14
    import {
      BaseDirectory,
      Configurate,
      JsonProvider,
      defineConfig,
      keyring,
      optional,
    } from "tauri-plugin-configurate-api";
    
    const schema = defineConfig({
      theme: String,
      fontSize: Number,
      notifications: optional(Boolean),
      server: {
        host: String,
        apiKey: keyring(String, { id: "server-api-key" }),
      },
    });
    
    15
    Schema value types at a glance:
    16
    ValueTypeScript typeStringstringNumbernumber (must be finite)Booleanbooleanoptional(schema)Wrapped type or undefined when absentkeyring(Type, { id })Actual type when unlocked; null when locked{ ... }Nested config object[Type]Array of that element type
    17
    Every keyring() call requires a unique id string within the schema. The id must not be empty or contain / — it is used as part of the OS keyring user string.
    18
    Create a Configurate instance
    19
    Instantiate Configurate with your schema, a file name, a base directory, and a storage provider. This object is your entry point to all CRUD operations.
    20
    const config = new Configurate({
      schema,
      fileName: "app.json",
      baseDir: BaseDirectory.AppConfig,
      provider: JsonProvider(),
    });
    
    21
    OptionWhat it doesfileNameThe config file name. Must not contain path separators.baseDirA Tauri BaseDirectory value. By default, only app-scoped directories (AppConfig, AppData, AppLocalData, etc.) are allowed over IPC.providerDetermines the on-disk format. JsonProvider() writes human-readable, pretty-printed JSON. Other options: YmlProvider(), TomlProvider(), and BinaryProvider(opts?).
    22
    You can create multiple Configurate instances with different fileName values to manage separate config files — for example, one for app-wide settings and another for per-user profiles.
    23
    Create (write) your first config
    24
    Call config.create(data) to write the initial configuration. When your schema contains keyring() fields, chain .lock(KEYRING) before calling .run() so the plugin knows which OS keyring service and account to store the secrets under.
    25
    const KEYRING = { service: "my-app", account: "default" };
    
    await config
      .create({
        theme: "dark",
        fontSize: 14,
        notifications: true,
        server: { host: "api.example.com", apiKey: "sk-secret" },
      })
      .lock(KEYRING)
      .run();
    
    26
  • .lock(KEYRING) separates the keyring fields from the rest of the payload and stores them in the OS keychain under the given service and account.
  • .run() executes the operation and returns a LockedConfig object where keyring fields appear as null.
  • If your schema has no keyring fields, you can skip .lock() and call .run() directly.
  • 27
    KeyringOptions (service and account) are yours to choose — typically your app name and a logical account label like "default". They must be non-empty and contain no control characters.
    28
    Load the config
    29
    Use config.load() to read the stored file. Chain .unlock(KEYRING) to populate keyring fields from the OS keychain, or call .run() to load in locked form (keyring fields will be null).
    30
    // Load with keyring secrets populated:
    const { data } = await config.load().unlock(KEYRING);
    console.log(data.theme);           // "dark"
    console.log(data.server.apiKey);   // "sk-secret" (from OS keyring)
    
    // Or load without unlocking (apiKey is null):
    const locked = await config.load().run();
    console.log(locked.data.server.apiKey); // null
    
    // You can also unlock later from a locked result:
    const unlocked = await locked.unlock(KEYRING);
    console.log(unlocked.data.server.apiKey); // "sk-secret"
    
    31
    Call unlocked.lock() when you are done with sensitive values to revoke access through that instance. After calling .lock(), any subsequent read of unlocked.data throws an error. This is an API-level access guard — JavaScript’s garbage collector manages the underlying memory.
    32
    Save the config
    33
    Use config.save(data) to fully replace the stored config with new data. Unlike patch, save overwrites every key — it is a complete replacement.
    34
    await config
      .save({
        theme: "light",
        fontSize: 16,
        notifications: false,
        server: { host: "api.example.com", apiKey: "sk-secret" },
      })
      .lock(KEYRING)
      .run();
    
    35
    Chain .lock(KEYRING) whenever the schema contains keyring fields, just as you would with create.
    36
    Update with patch
    37
    Use config.patch(partial) to update only the keys you provide. All other keys in the stored file are left untouched — this is a deep merge, not a full replacement.
    38
    await config.patch({ theme: "light" }).run();
    
    39
    If the config might not exist yet, chain .createIfMissing() to create it instead of throwing:
    40
    await config.patch({ theme: "light" }).createIfMissing().run();
    
    41
    When patching a keyring field, chain .lock(KEYRING) just as you would with create:
    42
    await config
      .patch({ server: { apiKey: "sk-newkey" } })
      .lock(KEYRING)
      .run();
    
    43
    Check existence and list configs
    44
    Use config.exists() to check whether the config file is present without loading its contents, and config.list() to enumerate all config files in the same directory.
    45
    const exists = await config.exists();
    console.log(exists); // true or false
    
    const files = await config.list();
    console.log(files); // e.g. ["app.json", "profiles.json"]
    
    46
    Delete the config
    47
    Use config.delete(keyringOpts?) to remove the config file. Pass your KeyringOptions to also delete the associated keyring entries so no secrets are left behind.
    48
    // Delete the file and clean up keyring entries:
    await config.delete(KEYRING);
    
    // Delete the file only (keyring entries are not removed):
    await config.delete();
    

    Complete example

    Here is the full end-to-end flow in a single TypeScript snippet you can drop straight into your Tauri frontend:
    import {
      BaseDirectory,
      Configurate,
      JsonProvider,
      defineConfig,
      keyring,
      optional,
    } from "tauri-plugin-configurate-api";
    
    // 1. Define schema
    const schema = defineConfig({
      theme: String,
      fontSize: Number,
      notifications: optional(Boolean),
      server: {
        host: String,
        apiKey: keyring(String, { id: "server-api-key" }),
      },
    });
    
    // 2. Create a Configurate instance
    const config = new Configurate({
      schema,
      fileName: "app.json",
      baseDir: BaseDirectory.AppConfig,
      provider: JsonProvider(),
    });
    
    // Keyring identity — reuse this object across all operations
    const KEYRING = { service: "my-app", account: "default" };
    
    // 3. Write the initial config (apiKey stored in OS keyring)
    await config
      .create({
        theme: "dark",
        fontSize: 14,
        notifications: true,
        server: { host: "api.example.com", apiKey: "sk-secret" },
      })
      .lock(KEYRING)
      .run();
    
    // 4a. Load with secrets populated
    const { data } = await config.load().unlock(KEYRING);
    console.log(data.theme);          // "dark"
    console.log(data.server.apiKey);  // "sk-secret"
    
    // 4b. Load locked (apiKey is null)
    const locked = await config.load().run();
    console.log(locked.data.server.apiKey); // null
    
    // 5. Save — full replacement
    await config
      .save({
        theme: "light",
        fontSize: 16,
        notifications: false,
        server: { host: "api.example.com", apiKey: "sk-secret" },
      })
      .lock(KEYRING)
      .run();
    
    // 6. Patch — only 'theme' is updated
    await config.patch({ theme: "dark" }).run();
    
    // Verify the patch
    const updated = await config.load().unlock(KEYRING);
    console.log(updated.data.theme);    // "dark"
    console.log(updated.data.fontSize); // 16  (unchanged)
    
    // 7. Check existence
    const exists = await config.exists();
    console.log(exists); // true
    
    // 8. Delete the config and its keyring entries
    await config.delete(KEYRING);
    

    Next steps

    Schema Definition

    Learn every schema value type: primitives, nested objects, arrays, optional fields, and keyring markers.

    Storage Providers

    Compare JSON, YAML, TOML, and encrypted Binary providers and choose the right one for your app.

    OS Keyring

    Understand how secrets are stored and retrieved from the native OS keychain on Linux, Windows, and macOS.

    CRUD Operations

    Deep-dive into create, load, save, patch, reset, and delete — including batch operations and file watching.