Guides

Rust backend

Use KeyringStore from your own commands, set the service name, plug in another keyring-core store, and read secrets from background tasks.

The plugin's state is available on every App, AppHandle and WebviewWindow through the KeyringExt trait. Full API: docs.rs.

use tauri_plugin_keyring_store::KeyringExt;

#[tauri::command]
fn save_api_token(app: tauri::AppHandle, token: String) -> Result<(), String> {
    app.keyring()
        .store
        .set_password("app.api_token", &token)
        .map_err(|e| e.to_string())
}

app.keyring() returns &KeyringPlugin:

FieldTypeWhat
storeArc<KeyringStore>OS store access for the plugin's service name.
sessionsSessionRegistrySession paths loaded with initialize (insert / remove / contains).

KeyringStore

MethodDoes
new(service)Handle for one service name. No I/O until the first call.
service()The service name.
set_password(account, &str) / get_password(account)UTF-8 secret; Ok(None) if missing.
set_bytes(account, &[u8]) / get_bytes(account)Bytes, stored as Base64 text.
delete(account)Deletes; a missing entry is Ok(()).
exists_nonempty(account)true if the secret is not empty after trimming.
account_store_key(session, client, key)Account of a session store record (kp:v1:…:st:…).
account_vault_record(session, client, vault, record)Account of a vault record (kp:v1:…:v:…).
account_raw(session, client, suffix)Account for a free-form key under a session and client (kp:v1:…:x:…).
with_store(store)Use another keyring-core store for this handle.
availability(), *_for_backgroundLocked-device helpers, see below.

Rust code is not limited by the kp: guard: it can read and write any account, including the session records.

Calls block

KeyringStore methods block: macOS can show a Keychain prompt, Linux talks to the Secret Service over D-Bus and may wait for an unlock prompt. In async commands, run them on a blocking thread:

#[tauri::command]
async fn read_token(app: tauri::AppHandle) -> Result<Option<String>, String> {
    tauri::async_runtime::spawn_blocking(move || app.keyring().store.get_password("app.api_token"))
        .await
        .map_err(|e| e.to_string())?
        .map_err(|e| e.to_string())
}

The plugin's own commands do this already.

Session records from Rust

The same hashing as the IPC store commands, so JavaScript sees what Rust writes:

use tauri_plugin_keyring_store::{BytesDto, KeyringExt};

let kr = app.keyring();
let client = BytesDto::Text("desktop".into());
let account = kr.store.account_store_key("/app/secrets", &client, "prefs");
kr.store.set_bytes(&account, b"{}")?;
// JS: (await (await KeyringSession.load("/app/secrets", "")).loadClient("desktop")).getStore().get("prefs")

Service name

init() uses the app identifier. To set it:

tauri_plugin_keyring_store::Builder::new()
    .service("com.example.myapp.keyring")
    .build()

A standalone KeyringStore::new("…") can use any other service, e.g. to share entries with another app or tool that uses the same service and account names.

Custom credential store

with_store points one handle at any keyring-core store instead of the process-wide default for the platform. The plugin registered with init() keeps the default store.

use tauri_plugin_keyring_store::KeyringStore;

let store = KeyringStore::new("com.example.app")
    .with_store(linux_keyutils_keyring_store::Store::new()?);
store.set_password("app.token", "secret")?;

Useful on Linux servers without a Secret Service (linux-keyutils-keyring-store never prompts, but keeps secrets only for the login session) and in tests (keyring_core::mock::Store).

Locked device and background reads

On iOS the keychain can be unreadable while the device is locked, see iOS.

CallWhen locked
get_password, get_bytes, exists_nonemptyErr(Error::KeychainLocked)
get_password_for_background, get_bytes_for_backgroundOk(None)
exists_nonempty_for_backgroundOk(false)
availability()KeyringAvailability::Locked
use tauri_plugin_keyring_store::{Error, KeyringAvailability, KeyringStore};

fn sync(store: &KeyringStore) -> Result<(), Error> {
    if store.availability() == KeyringAvailability::Locked {
        return Ok(()); // try again later
    }
    let token = store.get_password_for_background("oauth-token")?;
    Ok(())
}

On other platforms availability() always returns Available. These helpers have no IPC command; over IPC a locked keychain arrives as the error string keychain locked or protected data unavailable.

Errors

tauri_plugin_keyring_store::Error is #[non_exhaustive] and serializes to a plain string over IPC. Messages never contain secrets, key material or mnemonic words.

VariantMessage
SessionNotInitialized(path)session not initialized: <path>
NoEntrycredential entry not found
Init(reason)plugin initialization failed: <reason>: the OS store could not start.
KeychainLockedkeychain locked or protected data unavailable
Keyring(reason)keyring error: <reason>: any other OS store error.
Encoding(reason)encoding error: <reason>: bad Base64 in an entry, bad backup format.
Crypto(reason)crypto procedure error: <reason>
Naming(reason)naming / validation error: <reason>: account rules, item limits, naming helpers.

When the OS store fails to start (e.g. no D-Bus session yet), the failure is not cached: the next call after a 2-second pause tries again.