> ## Documentation Index
> Fetch the complete documentation index at: https://configurate.crystaworld.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Started with tauri-plugin-configurate in 5 Minutes

> Define a type-safe config schema, store and load application settings with JSON storage, and optionally protect sensitive fields with the OS keyring.

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`.

<Steps>
  ### Install the plugin

  Add the Rust crate to `src-tauri/Cargo.toml`:

  ```toml theme={null}
  [dependencies]
  tauri-plugin-configurate = "0.5.0"
  ```

  Register it in `src-tauri/src/lib.rs`:

  ```rust theme={null}
  fn main() {
      tauri::Builder::default()
          .plugin(tauri_plugin_configurate::init())
          .run(tauri::generate_context!())
          .expect("error while running tauri application");
  }
  ```

  Then install the JavaScript bindings with your package manager:

  ```sh theme={null}
  pnpm add tauri-plugin-configurate-api
  # or: npm add / yarn add / bun add
  ```

  Finally, grant the required permissions in your capability file (e.g. `src-tauri/capabilities/default.json`):

  ```json theme={null}
  {
    "permissions": ["configurate:default", "configurate:allow-unlock"]
  }
  ```

  <Note>
    `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.
  </Note>

  ### Define your schema

  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:

  * `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).

  ```typescript theme={null}
  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" }),
    },
  });
  ```

  **Schema value types at a glance:**

  | Value                   | TypeScript type                               |
  | ----------------------- | --------------------------------------------- |
  | `String`                | `string`                                      |
  | `Number`                | `number` (must be finite)                     |
  | `Boolean`               | `boolean`                                     |
  | `optional(schema)`      | Wrapped type or `undefined` when absent       |
  | `keyring(Type, { id })` | Actual type when unlocked; `null` when locked |
  | `{ ... }`               | Nested config object                          |
  | `[Type]`                | Array of that element type                    |

  <Tip>
    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.
  </Tip>

  ### Create a Configurate instance

  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.

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

  | Option     | What it does                                                                                                                                                               |
  | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `fileName` | The config file name. Must not contain path separators.                                                                                                                    |
  | `baseDir`  | A Tauri `BaseDirectory` value. By default, only app-scoped directories (`AppConfig`, `AppData`, `AppLocalData`, etc.) are allowed over IPC.                                |
  | `provider` | Determines the on-disk format. `JsonProvider()` writes human-readable, pretty-printed JSON. Other options: `YmlProvider()`, `TomlProvider()`, and `BinaryProvider(opts?)`. |

  <Note>
    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.
  </Note>

  ### Create (write) your first config

  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.

  ```typescript theme={null}
  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();
  ```

  * `.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.

  <Tip>
    `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.
  </Tip>

  ### Load the config

  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`).

  ```typescript theme={null}
  // 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"
  ```

  <Note>
    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.
  </Note>

  ### Save the config

  Use `config.save(data)` to fully replace the stored config with new data. Unlike `patch`, `save` overwrites every key — it is a complete replacement.

  ```typescript theme={null}
  await config
    .save({
      theme: "light",
      fontSize: 16,
      notifications: false,
      server: { host: "api.example.com", apiKey: "sk-secret" },
    })
    .lock(KEYRING)
    .run();
  ```

  Chain `.lock(KEYRING)` whenever the schema contains keyring fields, just as you would with `create`.

  ### Update with patch

  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.

  ```typescript theme={null}
  await config.patch({ theme: "light" }).run();
  ```

  If the config might not exist yet, chain `.createIfMissing()` to create it instead of throwing:

  ```typescript theme={null}
  await config.patch({ theme: "light" }).createIfMissing().run();
  ```

  When patching a keyring field, chain `.lock(KEYRING)` just as you would with `create`:

  ```typescript theme={null}
  await config
    .patch({ server: { apiKey: "sk-newkey" } })
    .lock(KEYRING)
    .run();
  ```

  ### Check existence and list configs

  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.

  ```typescript theme={null}
  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"]
  ```

  ### Delete the config

  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.

  ```typescript theme={null}
  // Delete the file and clean up keyring entries:
  await config.delete(KEYRING);

  // Delete the file only (keyring entries are not removed):
  await config.delete();
  ```
</Steps>

## Complete example

Here is the full end-to-end flow in a single TypeScript snippet you can drop straight into your Tauri frontend:

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="Schema Definition" icon="brackets-curly" href="/concepts/schema">
    Learn every schema value type: primitives, nested objects, arrays, optional fields, and keyring markers.
  </Card>

  <Card title="Storage Providers" icon="database" href="/concepts/providers">
    Compare JSON, YAML, TOML, and encrypted Binary providers and choose the right one for your app.
  </Card>

  <Card title="OS Keyring" icon="key" href="/concepts/keyring">
    Understand how secrets are stored and retrieved from the native OS keychain on Linux, Windows, and macOS.
  </Card>

  <Card title="CRUD Operations" icon="pen-to-square" href="/guides/crud">
    Deep-dive into create, load, save, patch, reset, and delete — including batch operations and file watching.
  </Card>
</CardGroup>
