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

# Create, Load, Save, and Delete Configuration Files

> Perform full create, read, update, and delete operations on configuration files using tauri-plugin-configurate's type-safe, keyring-aware API.

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.

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

<Tip>
  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:

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

***

## 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 method            | Description                                                    | Returns                      |
| -------------------------- | -------------------------------------------------------------- | ---------------------------- |
| `.lock(keyringOpts).run()` | Stores keyring secrets, returns locked data                    | `Promise<LockedConfig<S>>`   |
| `.unlock(keyringOpts)`     | Stores keyring secrets, returns unlocked data                  | `Promise<UnlockedConfig<S>>` |
| `.run()`                   | Runs without keyring (schemas with no `keyring()` fields only) | `Promise<LockedConfig<S>>`   |

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

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

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

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

<Steps>
  <Step title="Load the config">
    Call `config.load().run()` to read the file from disk. This is fast and
    does not touch the OS keyring.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Lock when done">
    Call `unlocked.lock()` to revoke access to the decrypted data through the
    current `UnlockedConfig` instance.
  </Step>
</Steps>

***

## save(data)

`config.save(data)` completely overwrites an existing config file with the data you provide. Every field — including keyring-protected ones — is replaced.

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

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

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

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

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