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

# OS Keyring Secret Storage in tauri-plugin-configurate

> Mark schema fields with keyring() to store sensitive values like passwords and API keys in the platform keychain instead of plain config files.

The keyring integration lets you keep sensitive values — API keys, passwords, tokens — out of your config files entirely. Instead of writing secrets to disk alongside your other settings, the plugin stores them in the platform's native secret store: **macOS Keychain**, **Windows Credential Manager**, or **Linux Secret Service / Keyutils**. Your config file on disk holds only non-sensitive fields; the secrets live in the OS and are retrieved on demand.

## Declaring keyring fields

Use `keyring()` in your schema to mark a field as keyring-protected. You must give each field a unique `id` string — this becomes part of the OS keyring entry identifier.

```typescript theme={null}
import { defineConfig, keyring, optional } from "tauri-plugin-configurate-api";

const schema = defineConfig({
  theme: String,
  apiKey: keyring(String, { id: "api-key" }),
});
```

See the [Schema](/concepts/schema) page for the full rules governing `keyring()` ids.

## `KeyringOptions`

Most keyring operations accept a `KeyringOptions` object that identifies which keyring entry to use:

```typescript theme={null}
interface KeyringOptions {
  service: string;  // Keyring service name (e.g. your app name)
  account: string;  // Keyring account name (e.g. "default")
}
```

Both `service` and `account` must be non-empty, non-whitespace strings without control characters. Use a consistent pair throughout your app so that all operations address the same keyring entries.

## Writing with `.lock()`

When you create or save a config that contains keyring fields, chain `.lock(keyringOpts)` before `.run()`. The plugin separates the secret values from the rest of the data, stores them in the OS keyring under `{account}/{id}`, and writes only the non-secret fields to the config file.

```typescript theme={null}
import {
  BaseDirectory,
  Configurate,
  JsonProvider,
  defineConfig,
  keyring,
} from "tauri-plugin-configurate-api";

const schema = defineConfig({
  theme: String,
  apiKey: keyring(String, { id: "api-key" }),
});

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

const KEYRING = { service: "my-app", account: "default" };

// Write — stores apiKey to the OS keyring; theme goes into app.json
await config.create({ theme: "dark", apiKey: "sk-abc123" }).lock(KEYRING).run();
```

## Reading with `.unlock()`

When you load a config that has keyring fields, chain `.unlock(keyringOpts)` to retrieve the secrets from the OS keyring and merge them back into the data object. The result is an `UnlockedConfig` instance whose `.data` property has keyring fields populated with their real values.

<Warning>
  `.unlock()` requires the `configurate:allow-unlock` permission, which is
  **not** included in `configurate:default`. You must add it explicitly to your
  capability file. See the [Permissions](/concepts/permissions) page for
  details.
</Warning>

```typescript theme={null}
// Read with secrets — requires configurate:allow-unlock permission
const { data } = await config.load().unlock(KEYRING);
console.log(data.apiKey); // "sk-abc123"
```

## Locked vs unlocked data

When you call `.run()` on a load (or skip `.unlock()`), you receive a `LockedConfig` instance. Keyring fields in `LockedConfig.data` are `null` — the secrets have not been retrieved from the OS.

```typescript theme={null}
// Read without secrets — no configurate:allow-unlock permission needed
const locked = await config.load().run();
console.log(locked.data.apiKey); // null
```

The TypeScript types reflect this distinction automatically. `InferLocked<S>` gives keyring fields the type `null`; `InferUnlocked<S>` gives them their real types (e.g. `string`).

## Unlocking a `LockedConfig` after the fact

If you already have a `LockedConfig` and want to retrieve secrets later, call `.unlock()` directly on it:

```typescript theme={null}
const locked = await config.load().run();

// ... do something with non-secret fields ...
console.log(locked.data.theme); // "dark"

// Unlock later when secrets are needed
const unlocked = await locked.unlock(KEYRING);
console.log(unlocked.data.apiKey); // "sk-abc123"
```

## Revoking access with `UnlockedConfig.lock()`

`UnlockedConfig` exposes a `.lock()` method that revokes access to the decrypted data through that instance. After calling it, any access to `.data` throws an error. Use this to reduce the window during which secrets are accessible in memory.

<Note>
  `UnlockedConfig.lock()` is an API-level access guard, not a cryptographic
  memory wipe. JavaScript's garbage collector controls when the underlying
  memory is actually reclaimed. Treat it as a structural safeguard to prevent
  accidental use of stale secret data, not as a guarantee of immediate memory
  clearing.
</Note>

```typescript theme={null}
const unlocked = await config.load().unlock(KEYRING);
console.log(unlocked.data.apiKey); // "sk-abc123"

unlocked.lock(); // revoke access

console.log(unlocked.data.apiKey); // throws: "Cannot access data after lock() has been called. Load or unlock again."
```

## Complete example

```typescript theme={null}
const schema = defineConfig({
  theme: String,
  apiKey: keyring(String, { id: "api-key" }),
});

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

const KEYRING = { service: "my-app", account: "default" };

// Write — stores apiKey to OS keyring
await config.create({ theme: "dark", apiKey: "sk-abc123" }).lock(KEYRING).run();

// Read with secrets
const { data } = await config.load().unlock(KEYRING);
console.log(data.apiKey); // "sk-abc123"

// Read without secrets
const locked = await config.load().run();
console.log(locked.data.apiKey); // null

// Unlock from a locked instance
const unlocked = await locked.unlock(KEYRING);
console.log(unlocked.data.apiKey); // "sk-abc123"
```

## Relationship to `BinaryProvider` encryption

The keyring integration and `BinaryProvider` encryption are independent features. Keyring fields are schema-level annotations that route specific values to the OS secret store. `BinaryProvider`'s `encryptionKey` encrypts the entire config file at rest. You can use both together, but the plugin never stores a `BinaryProvider` encryption key in the OS keyring automatically — managing that key is your responsibility.
