# tauri-plugin-configurate
**Tauri v2 plugin for type-safe application configuration management with OS keyring support.**
Store app settings as JSON, YAML, Binary, or SQLite — with sensitive values automatically secured in the OS keyring (Windows Credential Manager / macOS Keychain / Linux Secret Service).
---
> [!WARNING]
> **Pre-release software (0.x)**
>
> This plugin is under active development and has **not reached a stable 1.0 release**.
>
> - **Breaking changes may be introduced in any minor version** (e.g. 0.2 → 0.3).
> - **Bugs may be present.** Please report issues on [GitHub](https://github.com/Crysta1221/tauri-plugin-configurate/issues).
> - The on-disk format of **`BinaryProvider()` (unencrypted)** changed in v0.2.3 — existing files written by v0.2.2 or earlier must be re-created. Encrypted binary files (`BinaryProvider({ encryptionKey })`) are not affected.
>
> Pin to an exact version in production and review the [release notes](https://github.com/Crysta1221/tauri-plugin-configurate/releases) before upgrading.
---
## Features
| 🛡️ **Type-safe schema** | Define your config shape with `defineConfig()` — TypeScript infers all value types automatically |
| 🔑 **OS keyring integration** | Mark sensitive fields with `keyring()` — secrets never touch disk |
| 🧩 **Multiple providers** | Choose JSON, YAML, Binary (encrypted or plain), or SQLite as the storage backend |
| 📄 **Single-file API** | `create` / `load` / `save` / `delete` / `unlock` — consistent builder-style calls |
| 📦 **Batch API** | `loadAll` / `saveAll` — load or save multiple configs in a single IPC round-trip |
| 🗂️ **Flexible paths** | Control the storage location with `baseDir`, `options.dirName`, and `options.currentPath` |
---
## Installation
### 1. Add the Rust plugin
```toml
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-configurate = "0.x.x"
```
Register it in `src-tauri/src/lib.rs`:
```rust
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_configurate::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
**or, use tauri cli.**
```sh
tauri add configurate
```
### 2. Add the JavaScript / TypeScript API
```sh
# npm
npm install tauri-plugin-configurate-api
# pnpm
pnpm add tauri-plugin-configurate-api
# yarn
yarn add tauri-plugin-configurate-api
# bun
bun add tauri-plugin-configurate-api
```
> **Tip:** If you use the Tauri CLI, `tauri add configurate` handles both steps automatically.
### 3. Grant permissions
Add the following to your capability file (e.g. `src-tauri/capabilities/default.json`):
```json
{
"permissions": ["configurate:default"]
}
```
`configurate:default` expands to:
| `configurate:allow-create` | Create a new config file |
| `configurate:allow-load` | Read a config file |
| `configurate:allow-save` | Write/update a config file |
| `configurate:allow-delete` | Delete a config file |
| `configurate:allow-load-all` | Batch load |
| `configurate:allow-save-all` | Batch save |
| `configurate:allow-unlock` | Inline keyring decryption |
---
## Quick Start
### Step 1 — Define your schema
```ts
import { defineConfig, keyring } from "tauri-plugin-configurate-api";
const appSchema = defineConfig({
theme: String,
language: String,
database: {
host: String,
// "password" is stored in the OS keyring, never written to disk
password: keyring(String, { id: "db-password" }),
},
});
```
`defineConfig()` validates at runtime that all `keyring()` IDs are unique within the schema.
### Step 2 — Create a `Configurate` instance
```ts
import { BaseDirectory, Configurate, JsonProvider } from "tauri-plugin-configurate-api";
const config = new Configurate({
schema: appSchema,
fileName: "app.json", // filename only, no path separators
baseDir: BaseDirectory.AppConfig,
provider: JsonProvider(),
options: {
dirName: "my-app", // replaces the app identifier segment
currentPath: "config/v2", // sub-directory within the root
},
});
```
The resolved path is: `{AppConfig}/my-app/config/v2/app.json`
### Step 3 — Read and write
```ts
const KEYRING = { service: "my-app", account: "default" };
// Create — writes plain fields to disk, stores secrets in the OS keyring
await config
.create({
theme: "dark",
language: "ja",
database: { host: "localhost", password: "secret" },
})
.lock(KEYRING) // KEYRING opts are required when the schema has keyring fields
.run();
// Load (locked) — keyring fields come back as null
const locked = await config.load().run();
console.log(locked.data.database.password); // null
// Load (unlocked) — keyring fields are filled from the OS keyring
const unlocked = await config.load().unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"
// Save — same pattern as create
await config
.save({
theme: "light",
language: "en",
database: { host: "db.example.com", password: "next-secret" },
})
.lock(KEYRING)
.run();
// Delete — removes the file and wipes keyring entries
await config.delete(KEYRING);
```
---
## Providers
Choose the storage format when constructing a `Configurate` instance.
```ts
import {
JsonProvider,
YmlProvider,
BinaryProvider,
SqliteProvider,
} from "tauri-plugin-configurate-api";
// Plain JSON (human-readable)
JsonProvider();
// YAML
YmlProvider();
// Encrypted binary using XChaCha20-Poly1305
// The key is hashed via SHA-256 internally — use a high-entropy string
BinaryProvider({ encryptionKey: "high-entropy-key" });
// Unencrypted binary (compact JSON bytes, no human-readable format)
BinaryProvider();
// SQLite — all schema fields become typed columns
SqliteProvider({ dbName: "app.db", tableName: "configs" });
```
> [!NOTE]
> `BinaryProvider()` without an `encryptionKey` provides **no confidentiality**.
> Use `BinaryProvider({ encryptionKey })` or the OS keyring for sensitive values.
---
## Batch Operations
Load or save multiple configs in a **single IPC call** with `loadAll` / `saveAll`.
```ts
const appConfig = new Configurate({
schema: defineConfig({ theme: String }),
fileName: "app.json",
baseDir: BaseDirectory.AppConfig,
provider: JsonProvider(),
});
const secretConfig = new Configurate({
schema: defineConfig({ token: keyring(String, { id: "api-token" }) }),
fileName: "secret.bin",
baseDir: BaseDirectory.AppConfig,
provider: BinaryProvider({ encryptionKey: "high-entropy-key" }),
});
// Load all — unlock a specific entry by id
const loaded = await Configurate.loadAll([
{ id: "app", config: appConfig },
{ id: "secret", config: secretConfig },
])
.unlock("secret", { service: "my-app", account: "default" })
.run();
// Save all — lock a specific entry by id
const saved = await Configurate.saveAll([
{ id: "app", config: appConfig, data: { theme: "dark" } },
{ id: "secret", config: secretConfig, data: { token: "next-token" } },
])
.lock("secret", { service: "my-app", account: "default" })
.run();
```
### Result shape
Each entry in `results` is either a success or a per-entry failure — a single entry failing **does not abort the batch**.
```ts
type BatchRunResult = {
results: {
[id: string]:
| { ok: true; data: unknown }
| { ok: false; error: { kind: string; message: string } };
};
};
// Access individual results
loaded.results.app; // { ok: true, data: { theme: "dark" } }
loaded.results.secret; // { ok: true, data: { token: "..." } }
```
---
## Path Resolution
| `baseDir` | Tauri `BaseDirectory` enum value (e.g. `AppConfig`, `AppData`, `Desktop`) |
| `options.dirName` | Replaces the app-identifier segment when it is the last segment of `baseDir` path; otherwise appended as a sub-directory |
| `options.currentPath` | Sub-directory appended after the `dirName` root |
| `fileName` | Single filename — **must not contain path separators** |
Example — `baseDir: AppConfig`, `dirName: "my-app"`, `currentPath: "v2"`, `fileName: "settings.json"`:
```
Windows: C:\Users\<user>\AppData\Roaming\my-app\v2\settings.json
macOS: ~/Library/Application Support/my-app/v2/settings.json
Linux: ~/.config/my-app/v2/settings.json
```
---
## Compatibility
The following **deprecated** forms are still accepted in the current minor version and automatically normalized to the new API. Each emits one `console.warn` per process.
| `new Configurate(schema, opts)` | `new Configurate({ schema, ...opts })` |
| `ConfigurateFactory` | `new Configurate({ ... })` |
| `dir` | `baseDir` |
| `name` | `fileName` |
| `path` | `options.currentPath` |
| `format` + `encryptionKey` | `provider: JsonProvider()` / `BinaryProvider({ encryptionKey })` |
> These compatibility shims will be **removed in the next minor release**.
---
## License
MIT © [Crysta1221](https://github.com/Crysta1221)