Password accounts
The password commands work on raw account names that you choose. Each account is one OS keyring entry under your service name, with the secret stored as the UTF-8 text you pass. No session is needed.
import { setPasswords, getPasswords, deletePasswords, passwordExists, joinKeyPrefix } from "tauri-plugin-keyring-store-api";
const account = joinKeyPrefix("app", "api_token"); // "app.api_token"
await setPasswords([{ account, secret: "secret-value" }]);
const values = await getPasswords([account, "app.missing"]); // ["secret-value", null]
const exists = await passwordExists(account); // true
await deletePasswords([account]);
| Function | Does |
|---|---|
getPasswords(accounts) | Secrets in the input order; null for a missing account. |
setPasswords(entries) | Writes { account, secret } pairs, replacing existing secrets. |
deletePasswords(accounts) | Deletes; a missing account is not an error. |
passwordExists(account) | true if the account holds a secret that is not empty after trimming. |
Account rules
Every account is checked before anything is read or written:
- Leading and trailing spaces are trimmed; the result must not be empty.
- At most 512 bytes.
- At most 256 accounts or entries per call.
- No
kp:prefix, see Reserved accounts.
A bad account rejects the whole call with naming / validation error: …, and setPasswords writes
nothing. A keyring error in the middle of a batch (e.g. a Windows size limit) stops at that entry:
the earlier entries are already written.
Reserved accounts
Accounts that start with kp: in any letter case (kp:, KP:, Kp:, …) hold the
session store records and vault keys. The password
commands refuse them, so the webview cannot read vault key material with getPasswords or overwrite
it with setPasswords. Letter case is ignored because Windows credential names are case-insensitive.
Naming
The OS stores cannot list or search entries. A simple convention helps you keep track: prefix.name
with exactly one dot.
import { joinKeyPrefix, splitKeyPrefix, KEYRING_PREFIX_SEPARATOR } from "tauri-plugin-keyring-store-api";
const account = joinKeyPrefix("billing", "stripe_sk"); // "billing.stripe_sk"
const [prefix, name] = splitKeyPrefix(account); // ["billing", "stripe_sk"]
KEYRING_PREFIX_SEPARATOR; // "."
Both trim their input and throw if a part is empty or contains a dot. Rust has the same helpers:
join_prefix, split_prefixed and PREFIX_SEPARATOR. The convention is only for your code; the
plugin accepts any account that follows the rules. Keep your own index of accounts if
you need to enumerate them.
Backups
Both formats contain the accounts you list. A missing account is exported with secret: null and
skipped on import. An import is validated as a whole (format version 1, at most 256 rows, every account
follows the rules) before the first write, so a bad backup changes nothing.
Plain
import { exportPasswordsPlain, importPasswordsPlain } from "tauri-plugin-keyring-store-api";
const backup = await exportPasswordsPlain(["app.api_token", "app.refresh_token"]);
// { formatVersion: 1, entries: [{ account: "app.api_token", secret: "…" }, …] }
await importPasswordsPlain(backup);
The secrets cross IPC in clear text and live in webview memory. Use it only in trusted UI flows, or use the encrypted format.
Encrypted
import { exportPasswordsEncrypted, importPasswordsEncrypted } from "tauri-plugin-keyring-store-api";
const backup = await exportPasswordsEncrypted(["app.api_token"], "user passphrase");
// { formatVersion: 1, salt: "…", nonce: "…", ciphertext: "…" } (Base64)
await importPasswordsEncrypted(backup, "user passphrase");
The key is derived from the passphrase with Argon2id (19 MiB memory, 3 iterations, 1 lane, 16-byte random salt) and the plain backup JSON is encrypted with ChaCha20-Poly1305 (12-byte random nonce). Encryption runs in Rust; the webview sees only the envelope. A wrong passphrase or a modified envelope rejects and writes nothing. Backups made by 0.1.x still decrypt.
Encrypted backups are always compiled, with or without the crypto feature.