Skip to main content
BinaryProvider gives you optional at-rest encryption for your config files. When you supply an encryptionKey, the plugin encrypts every write and decrypts every read using XChaCha20-Poly1305 authenticated encryption — a construction that provides both confidentiality and integrity. Choosing the right key-derivation function (KDF) for your situation is the single most important decision when enabling encryption.

What BinaryProvider encrypts

BinaryProvider stores config data as compact binary. Without an encryptionKey the bytes are unencrypted but still not human-readable JSON or YAML. When you add a key:
  • Every file write is sealed with XChaCha20-Poly1305, an authenticated encryption scheme that detects tampering.
  • The raw bytes on disk are opaque — no field names, no values, no structure are legible without the key.
  • If you need to inspect values during development, use exportAs() to decode the config into JSON, YAML, or TOML in memory.

Choosing a KDF

The plugin supports two key-derivation strategies. Pick based on where the key comes from — not on preference.
ScenarioKDFCode
Random / high-entropy key (env var, OS keyring)"sha256" (default)BinaryProvider({ encryptionKey: "key" })
User-entered password"argon2"BinaryProvider({ encryptionKey: password, kdf: "argon2" })
No encryption neededBinaryProvider()
SHA-256 KDF feeds your key directly into a single SHA-256 hash — no salt, no iteration, no memory-hardening. This is safe only when the input key is already high-entropy (e.g. 32 random bytes fetched from the OS keyring or an environment variable). Never use it with a user-typed password. Argon2id KDF generates a random salt per file and runs the Argon2id memory-hard function before keying the cipher. The salt is stored alongside the ciphertext, so encryption and decryption are self-contained. This is the correct choice whenever the key comes from a human-readable password.
Never pass a user-entered password to BinaryProvider without setting kdf: "argon2". The default "sha256" KDF applies no salt or stretching, making dictionary and brute-force attacks trivial against low-entropy passwords. If your key comes from a keyring, environment variable, or a CSPRNG, "sha256" is appropriate. When in doubt, use "argon2".

Basic usage

The example below shows a high-entropy key (for example, one retrieved from the OS keyring at startup) used with the default SHA-256 KDF.
import {
  BaseDirectory,
  Configurate,
  BinaryProvider,
  defineConfig,
} from "tauri-plugin-configurate-api";

const schema = defineConfig({
  apiKey: String,
  settings: { theme: String },
});

// High-entropy key — e.g. fetched from the OS keyring or an env var
const config = new Configurate({
  schema,
  fileName: "app.bin",
  baseDir: BaseDirectory.AppConfig,
  provider: BinaryProvider({ encryptionKey: "a-high-entropy-random-key" }),
});

await config.create({ apiKey: "secret", settings: { theme: "dark" } }).run();

Password-based encryption

When the key originates from user input, switch to kdf: "argon2". The call signature is otherwise identical.
import {
  BaseDirectory,
  Configurate,
  BinaryProvider,
  defineConfig,
} from "tauri-plugin-configurate-api";

const schema = defineConfig({
  apiKey: String,
  settings: { theme: String },
});

// User-supplied password — always use Argon2id
const userPassword = "my-app-password";

const config = new Configurate({
  schema,
  fileName: "app.bin",
  baseDir: BaseDirectory.AppConfig,
  provider: BinaryProvider({ encryptionKey: userPassword, kdf: "argon2" }),
});

await config.create({ apiKey: "secret", settings: { theme: "dark" } }).run();

Combining with keyring fields

File-level encryption and keyring() schema fields are completely independent features. You can use them together: the file is encrypted at rest, and individual sensitive fields are also stored in the OS platform keychain. This is useful when you want general config privacy plus extra protection for high-value secrets like tokens or master keys.
BinaryProvider encryption and keyring() fields protect data through separate mechanisms. Encrypting the file does not remove the need for keyring fields, and vice versa. Use both when your threat model calls for defence-in-depth.
import {
  BaseDirectory,
  Configurate,
  BinaryProvider,
  defineConfig,
  keyring,
} from "tauri-plugin-configurate-api";

const schema = defineConfig({
  masterKey: keyring(String, { id: "master-key" }),
  settings: { theme: String },
});

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

const config = new Configurate({
  schema,
  fileName: "app.bin",
  baseDir: BaseDirectory.AppConfig,
  provider: BinaryProvider({ encryptionKey: "file-encryption-key" }),
});

// masterKey goes to the OS keychain; settings go into the encrypted file
await config
  .create({ masterKey: "keyring-secret", settings: { theme: "dark" } })
  .lock(KEYRING)
  .run();

Exporting encrypted configs

Because encrypted binary files are not human-readable, use exportAs() to decode them into a readable format for debugging or inspection. The decryption happens in memory — nothing unencrypted is written back to disk.
// Decrypt in memory and format as JSON
const json = await config.exportAs("json");
console.log(json); // { "apiKey": "secret", "settings": { "theme": "dark" } }

// Export as YAML instead
const yaml = await config.exportAs("yml");
You can also export with keyring fields unlocked by passing KeyringOptions as the second argument:
const KEYRING = { service: "my-app", account: "default" };
const jsonWithSecrets = await config.exportAs("json", KEYRING);
The encryptionKey travels over Tauri IPC only for operations that actually read or write the encrypted file: load, create, save, and patch. It is intentionally omitted from the IPC payload for delete and exists, which do not need to decrypt the content. Restricting DevTools in production builds is still recommended to minimise the IPC surface area exposed to potential inspection.