# How to Manage State in Tauri Applications Between Frontend and Backend

> Discover how to manage state in Tauri apps. Learn to effortlessly share data between your JS frontend and Rust backend using the State T wrapper and Builder manage API for efficient Tauri development.

- Repository: [Tauri/tauri](https://github.com/tauri-apps/tauri)
- Tags: how-to-guide
- Published: 2026-02-26

---

**Tauri enables seamless state sharing between your JavaScript frontend and Rust backend through the `State<T>` wrapper and `Builder::manage` API, which stores type-registered data in a global `StateManager` accessible from any command.**

Managing shared state across the frontend-backend boundary is essential for building complex desktop applications with Tauri. The `tauri-apps/tauri` repository provides a type-safe, thread-safe solution that leverages Rust's ownership model to prevent data races while allowing JavaScript to invoke Rust functions that operate on persistent data. This guide explains how to implement state management using the official APIs defined in [`crates/tauri/src/state.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/state.rs) and [`crates/tauri/src/app.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/app.rs).

## Understanding the State<T> Architecture

Tauri's state management centers on the **`State<'r, T>`** guard and the **`StateManager`** struct. When you register a state value using `Builder::manage` (lines 1794-1804 in [`crates/tauri/src/app.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/app.rs)), the system stores your data in a `HashMap<TypeId, Pin<Box<dyn Any + Send + Sync>>>` that maps concrete Rust types to their instances. This design guarantees a **single source of truth** per type—attempting to register the same type twice triggers a panic, preventing accidental state duplication.

### Thread Safety and Concurrency Guarantees

Because the underlying `StateManager` must safely handle concurrent access from multiple commands, your state type must implement `Send + Sync`. The manager's internal map is protected by a `Mutex`, and the `State<T>` wrapper implements `Deref` to provide direct access to the inner value. When a command is invoked, `State::from_command` (lines 60-68 in [`crates/tauri/src/state.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/state.rs)) extracts the state from the manager; if the type was not previously registered, it returns an `InvokeError` rather than panicking.

### Lifetime and Storage Model

State registered via `.manage()` lives for the application's entire lifetime in a `static`-like container. The `State<'r, T>` guard carries a lifetime matching the command's request scope, ensuring you cannot leak references outside the command, while the underlying data persists across multiple frontend invocations until the application terminates.

## Registering and Accessing Application State

### Step 1: Register State with manage()

Register your state container during application initialization:

```rust
use std::sync::Mutex;
use tauri::State;

struct Counter(Mutex<isize>);

fn main() {
    tauri::Builder::default()
        .manage(Counter(Mutex::new(0)))  // Registers with StateManager
        .invoke_handler(tauri::generate_handler![increment, get])
        .run(tauri::generate_context!())
        .expect("failed to run app");
}

```

The `manage` method calls `StateManager::set` internally, inserting your value into the global map using its `TypeId` as the key.

### Step 2: Access State in Commands

Retrieve registered state by adding `State<'_, T>` as a function parameter:

```rust
#[tauri::command]
fn increment(counter: State<'_, Counter>) -> isize {
    let mut c = counter.0.lock().unwrap();
    *c += 1;
    *c
}

#[tauri::command]
fn get(counter: State<'_, Counter>) -> isize {
    *counter.0.lock().unwrap()
}

```

The runtime automatically injects the correct instance when the frontend invokes these commands via the Tauri API.

### Step 3: Access State from Non-Command Contexts

For background threads or setup hooks, use the `Manager` trait's `state()` method on `AppHandle`:

```rust
use tauri::Manager;

fn spawn_worker<R: tauri::Runtime>(handle: tauri::AppHandle<R>) {
    std::thread::spawn(move || {
        let state: State<'_, Counter> = handle.state();
        // Access state here
    });
}

```

This approach leverages the same `StateManager` but allows access from anywhere you hold an `AppHandle`.

## Practical Implementation Examples

### Example 1: Thread-Safe Counter

The official example demonstrates managing mutable state with a `Mutex`:

```rust
// src-tauri/src/main.rs
use std::sync::Mutex;
use tauri::State;

struct Counter(Mutex<isize>);

#[tauri::command]
fn increment(counter: State<'_, Counter>) -> isize {
    let mut c = counter.0.lock().unwrap();
    *c += 1;
    *c
}

#[tauri::command]
fn get(counter: State<'_, Counter>) -> isize {
    *counter.0.lock().unwrap()
}

fn main() {
    tauri::Builder::default()
        .manage(Counter(Mutex::new(0)))
        .invoke_handler(tauri::generate_handler![increment, get])
        .run(tauri::generate_context!())
        .expect("failed to run app");
}

```

Invoke from your frontend JavaScript:

```javascript
import { invoke } from '@tauri-apps/api/tauri';

async function increase() {
  const value = await invoke('increment');
  console.log('Counter is now', value);
}

```

### Example 2: Database Connection Management

For SQLite or other database connections, register the connection handle directly (ensure thread-safety via connection pooling or the `shared` feature for SQLite):

```rust
use tauri::State;
use rusqlite::Connection;

struct DbConnection {
    conn: Connection,
}

#[tauri::command]
fn list_users(db: State<'_, DbConnection>) -> Result<Vec<String>, String> {
    let mut stmt = db.conn.prepare("SELECT name FROM users")
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map([], |row| row.get(0))
        .map_err(|e| e.to_string())?;
    rows.collect()
}

#[tauri::command]
fn add_user(name: String, db: State<'_, DbConnection>) -> Result<(), String> {
    db.conn
        .execute("INSERT INTO users (name) VALUES (?1)", [&name])
        .map_err(|e| e.to_string())?;
    Ok(())
}

fn main() {
    let conn = Connection::open_in_memory().unwrap();
    conn.execute(
        "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
        [],
    ).unwrap();

    tauri::Builder::default()
        .manage(DbConnection { conn })
        .invoke_handler(tauri::generate_handler![list_users, add_user])
        .run(tauri::generate_context!())
        .expect("run error");
}

```

### Example 3: Background Thread State Access

Access global state from spawned threads using `AppHandle::state()`:

```rust
use std::thread;
use tauri::{Manager, State};

struct SharedData(Mutex<Vec<String>>);

fn spawn_background_task<R: tauri::Runtime>(app_handle: tauri::AppHandle<R>) {
    thread::spawn(move || {
        let state: State<'_, SharedData> = app_handle.state();
        let mut data = state.0.lock().unwrap();
        data.push("background entry".into());
    });
}

fn main() {
    tauri::Builder::default()
        .manage(SharedData(Mutex::new(vec![])))
        .setup(|app| {
            spawn_background_task(app.handle().clone());
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("run error");
}

```

## Key Implementation Files in the Tauri Source

| File | Purpose | Key Components |
|------|---------|----------------|
| [`crates/tauri/src/state.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/state.rs) | State guard and manager | `State<'r, T>`, `StateManager`, `State::from_command` |
| [`crates/tauri/src/app.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/app.rs) (lines 1794-1804) | State registration | `Builder::manage`, `StateManager::set` |
| [`crates/tauri/src/lib.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/lib.rs) | Manager trait | `Manager::state<T>()` for `AppHandle` access |
| [`examples/state/main.rs`](https://github.com/tauri-apps/tauri/blob/main/examples/state/main.rs) | Reference implementation | Complete working counter example |

## Summary

- **Type-safe registration**: Use `Builder::manage` in [`crates/tauri/src/app.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/app.rs) to register exactly one instance per concrete type, stored in a `HashMap<TypeId>` within `StateManager`.
- **Command injection**: Access state via `State<'_, T>` parameters; the runtime extracts values using `State::from_command` and returns `InvokeError` for unregistered types.
- **Thread safety**: Wrap mutable data in `Mutex` or `RwLock` to satisfy `Send + Sync` bounds required by Tauri's multi-threaded command runtime.
- **Global access**: Use `AppHandle::state()` from the `Manager` trait to access state in background threads, setup hooks, or other non-command contexts.
- **Lifetime safety**: State persists for the application's `'static` lifetime but cannot be leaked outside command scope due to the `'r` lifetime constraint on `State<'r, T>`.

## Frequently Asked Questions

### How does Tauri prevent duplicate state registrations?

The `StateManager` internally uses a `HashMap<TypeId, Pin<Box<dyn Any + Send + Sync>>>` where each type can only occupy one entry. When `Builder::manage` calls `StateManager::set`, the system checks for existing entries and panics if the type has already been registered, enforcing a single source of truth per concrete type according to the implementation in [`crates/tauri/src/app.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/app.rs).

### Can I use State<T> with async commands?

Yes. The `State<'_, T>` extractor works with both synchronous and asynchronous command handlers because the underlying data lives for the `'static` lifetime of the application. Ensure your state type implements `Send` if it will be held across await points in multi-threaded runtimes, matching the `Send + Sync` bounds required by `StateManager`.

### What happens if I request a state type that wasn't registered?

When a command requests `State<'_, T>` for an unregistered type `T`, the `State::from_command` method (lines 60-68 in [`crates/tauri/src/state.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri/src/state.rs)) fails to find the type in the manager's map and returns an `InvokeError`. This error propagates to the frontend as a rejected promise, allowing graceful error handling rather than causing a runtime panic.

### Is State<T> suitable for per-window data?

No. `State<T>` is global to the entire application instance and managed by the single `StateManager`. For per-window or per-webview state, use the `Window` or `Webview` APIs provided by Tauri, which allow attaching data specific to individual windows. The `State` system is designed specifically for shared application-wide resources like database connections, configuration caches, or global counters.