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

# Defining Config Schemas with Full Type Safety in Tauri

> Learn how defineConfig(), keyring(), and optional() build a type-inferred config schema for Tauri apps, covering primitives, nested objects, and arrays.

A schema is the single source of truth for the shape of your configuration. You declare it once using `defineConfig()`, and TypeScript uses it to infer exact types for every read and write operation — eliminating entire categories of bugs where stored data diverges from what your code expects.

## `defineConfig(schema)`

`defineConfig()` accepts a plain schema object and returns it unchanged. Its only job is to act as a type anchor so TypeScript can infer the configuration shape throughout your app. Call it once at module level and export the result.

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

const schema = defineConfig({
  theme: String,
  fontSize: optional(Number),
  apiKey: keyring(String, { id: "api-key" }),
  database: {
    host: String,
    port: Number,
    password: keyring(String, { id: "db-password" }),
  },
  tags: [String],
});
```

## Schema value types

Every field in your schema object must be one of the following value types:

| Type                    | Description                                   |
| ----------------------- | --------------------------------------------- |
| `String`                | String field                                  |
| `Number`                | Number field (must be finite)                 |
| `Boolean`               | Boolean field                                 |
| `keyring(Type, { id })` | OS keyring-protected field                    |
| `optional(Type)`        | Optional field (may be `undefined` or `null`) |
| `{ ... }`               | Nested object                                 |
| `[Type]`                | Array of a single element type                |

Arrays must contain exactly one element — the element type — such as `[String]` or `[{ name: String }]`. This single-element tuple syntax is how the plugin knows what type to enforce for every item in the array.

## Type inference

The plugin exports two utility types that derive TypeScript interfaces directly from your schema:

* **`InferUnlocked<S>`** — The full type with keyring fields as their real value types. Use this when you have access to decrypted data.
* **`InferLocked<S>`** — The type with keyring fields replaced by `null`. This is what you receive from `.run()` after a load or create, before calling `.unlock()`.

```typescript theme={null}
import type { InferUnlocked, InferLocked } from "tauri-plugin-configurate-api";

type Config = InferUnlocked<typeof schema>;
// {
//   theme: string;
//   fontSize?: number;
//   apiKey: string;
//   database: { host: string; port: number; password: string };
//   tags: string[];
// }

type LockedConfig = InferLocked<typeof schema>;
// {
//   theme: string;
//   fontSize?: number;
//   apiKey: null;
//   database: { host: string; port: number; password: null };
//   tags: string[];
// }
```

## `keyring(typeCtor, { id })`

`keyring()` marks a field so its value is stored in the OS keychain instead of the config file on disk. At runtime the config file contains a placeholder; the real value is written to and read from the platform keyring.

```typescript theme={null}
keyring(String, { id: "api-key" })
keyring(Number, { id: "pin-code" })
keyring(Boolean, { id: "consent-flag" })
```

The `id` must satisfy two rules:

* It must be a **non-empty** string.
* It must **not contain `/`**, because the plugin uses it as part of the OS keyring user string in the form `{account}/{id}`.

Each `id` must also be **unique within the same schema**. Reusing an id is a compile-time error enforced by TypeScript's type system via `HasDuplicateKeyringIds`, and a runtime error thrown by `defineConfig()`:

```
Duplicate keyring id: 'api-key'. Each keyring() call must use a unique id within the same schema.
```

## `optional(schema)`

`optional()` wraps any schema value to indicate the field may be absent from stored data. Validation will not fail when the field is missing, and the inferred TypeScript type includes `| undefined`.

`optional()` can wrap any of the following:

* A primitive constructor: `optional(Number)`
* A keyring field: `optional(keyring(String, { id: "opt-secret" }))`
* A nested object: `optional({ host: String, port: Number })`
* An array: `optional([String])`

```typescript theme={null}
const schema = defineConfig({
  theme: String,                              // required string
  fontSize: optional(Number),                 // optional number
  proxy: optional({ host: String, port: Number }), // optional nested object
  aliases: optional([String]),                // optional array of strings
  vaultKey: optional(keyring(String, { id: "vault" })), // optional keyring field
});
```
