# Tauri Core Runtime

> Use when configuring app initialization, using setup hooks, spawning background tasks, or managing app lifecycle. Prevents misconfigured Builder chains and missing plugin registration that cause silent runtime failures. Covers Builder configuration, setup hook, AppHandle usage, Manager trait, tokio async runtime, and app exit/restart. Keywords: tauri builder, setup hook, AppHandle, Manager trait, tokio runtime, app lifecycle, window events, app startup, initialization, background task, app lifecycle, setup hook, async Rust..

- Skill: `impertio-studio/tauri-core-runtime` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/tauri-core-runtime`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/tauri-core-runtime/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/tauri-core-runtime

---


# tauri-core-runtime

## Quick Reference

### Builder Method Summary (Tauri 2.x)

| Method | Purpose | Example |
|--------|---------|---------|
| `Builder::default()` | Create default builder | `tauri::Builder::default()` |
| `.setup(F)` | App initialization hook | `.setup(\|app\| { Ok(()) })` |
| `.invoke_handler(F)` | Register command handlers | `.invoke_handler(generate_handler![cmd1])` |
| `.manage(T)` | Register managed state | `.manage(MyState::default())` |
| `.plugin(P)` | Register a plugin | `.plugin(my_plugin::init())` |
| `.menu(F)` | Set application menu | `.menu(\|app\| MenuBuilder::new(app).build())` |
| `.on_window_event(F)` | Window event handler | `.on_window_event(\|window, event\| { })` |
| `.on_menu_event(F)` | Menu event handler | `.on_menu_event(\|app, event\| { })` |
| `.on_webview_event(F)` | Webview event handler | `.on_webview_event(\|webview, event\| { })` |
| `.on_page_load(F)` | Page load handler | `.on_page_load(\|webview, payload\| { })` |
| `.on_tray_icon_event(F)` | Tray icon event handler | `.on_tray_icon_event(\|app, event\| { })` |
| `.any_thread()` | Allow running on non-main thread | `.any_thread()` |
| `.build(Context)` | Build without running (returns `App`) | `.build(generate_context!())` |
| `.run(Context)` | Build and run the app | `.run(generate_context!())` |

### Manager Trait Key Methods

| Method | Returns | Available On |
|--------|---------|-------------|
| `app_handle()` | `&AppHandle<R>` | App, AppHandle, Window, WebviewWindow, Webview |
| `state::<T>()` | `State<'_, T>` | All Manager implementors |
| `try_state::<T>()` | `Option<State<'_, T>>` | All Manager implementors |
| `manage(T)` | `bool` | All Manager implementors |
| `get_webview_window(label)` | `Option<WebviewWindow<R>>` | All Manager implementors |
| `webview_windows()` | `HashMap<String, WebviewWindow<R>>` | All Manager implementors |
| `get_window(label)` | `Option<Window<R>>` | All Manager implementors |
| `get_focused_window()` | `Option<Window<R>>` | All Manager implementors |
| `config()` | `&Config` | All Manager implementors |
| `path()` | `&PathResolver<R>` | All Manager implementors |
| `package_info()` | `&PackageInfo` | All Manager implementors |

### AppHandle Properties

| Property | Description |
|----------|-------------|
| `Clone` | Can be cloned freely |
| `Send + Sync` | Safe to pass across threads |
| Implements `Manager` | Full access to state, windows, config |
| Implements `Emitter` | Can emit events |
| Implements `Listener` | Can listen for events |

### Critical Warnings

**NEVER** call `.invoke_handler()` more than once on a Builder -- only the last call takes effect. Put ALL commands in a single `generate_handler![]`.

**NEVER** use `Arc` to wrap managed state -- Tauri already wraps state in `Arc` internally. Use `app.manage(data)`, not `app.manage(Arc::new(data))`.

**NEVER** access managed state with the wrong wrapper type -- `State<'_, MyState>` when you registered `Mutex<MyState>` causes a runtime panic, not a compile error.

**ALWAYS** clone `AppHandle` before moving it into a thread or async block -- `app.handle().clone()` is the correct pattern.

**ALWAYS** use `#[cfg_attr(mobile, tauri::mobile_entry_point)]` on the `run()` function in `lib.rs` when targeting mobile platforms.

**NEVER** perform heavy I/O in `setup()` synchronously without spawning a thread -- it blocks app startup.

---

## Essential Patterns

### Pattern 1: Standard Application Entry Point (Tauri 2.x)

```rust
// src-tauri/src/lib.rs
mod commands;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(AppState::default())
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![
            commands::greet,
            commands::fetch_data,
        ])
        .setup(|app| {
            let handle = app.handle().clone();
            // Initialization logic here
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

```rust
// src-tauri/src/main.rs
fn main() {
    app_lib::run();
}
```

### Pattern 2: The setup() Hook

The setup hook runs after the app is initialized but before windows are shown. It receives `&mut App<R>` and returns `Result<(), Box<dyn Error>>`. Any error aborts app startup.

```rust
.setup(|app| {
    // 1. Access AppHandle for thread-safe operations
    let handle = app.handle().clone();

    // 2. Register state that depends on app paths
    let db_path = app.path().app_data_dir()?.join("data.db");
    app.manage(Database::new(&db_path)?);

    // 3. Spawn a background thread (uses cloned AppHandle)
    std::thread::spawn(move || {
        loop {
            handle.emit("heartbeat", ()).unwrap();
            std::thread::sleep(std::time::Duration::from_secs(30));
        }
    });

    // 4. Create additional windows programmatically
    let _settings = tauri::webview::WebviewWindowBuilder::new(
        app,
        "settings",
        tauri::WebviewUrl::App("settings.html".into()),
    )
    .title("Settings")
    .inner_size(600.0, 400.0)
    .visible(false)
    .build()?;

    Ok(())
})
```

### Pattern 3: AppHandle in Threads and Async Blocks

`AppHandle` is `Clone + Send + Sync`, making it the primary handle for background work.

```rust
// In setup() or from a command
let handle = app.handle().clone();

// Std thread
std::thread::spawn(move || {
    let state = handle.state::<MyState>();
    handle.emit("background-update", "data").unwrap();
    if let Some(window) = handle.get_webview_window("main") {
        window.set_title("Updated").unwrap();
    }
});

// Tokio async task
let handle = app.handle().clone();
tokio::spawn(async move {
    let result = some_async_work().await;
    handle.emit("async-done", result).unwrap();
});
```

### Pattern 4: Window Event Callbacks

```rust
.on_window_event(|window, event| {
    match event {
        tauri::WindowEvent::CloseRequested { api, .. } => {
            // Prevent close and hide instead (e.g., tray app)
            api.prevent_close();
            window.hide().unwrap();
        }
        tauri::WindowEvent::Focused(focused) => {
            if *focused {
                println!("{} gained focus", window.label());
            }
        }
        tauri::WindowEvent::Destroyed => {
            println!("{} was destroyed", window.label());
        }
        _ => {}
    }
})
```

### Pattern 5: Build vs Run

Use `.build()` when you need access to the `App` instance before running, or when you need the run callback for lifecycle events like exit.

```rust
// .run() -- simple, no run callback
tauri::Builder::default()
    .run(tauri::generate_context!())
    .expect("error running app");

// .build() + .run() -- access to RunEvent for exit handling
let app = tauri::Builder::default()
    .build(tauri::generate_context!())
    .expect("error building app");

app.run(|app_handle, event| {
    match event {
        tauri::RunEvent::ExitRequested { api, .. } => {
            // Prevent exit (e.g., keep running in tray)
            api.prevent_exit();
        }
        tauri::RunEvent::Exit => {
            // Final cleanup before process ends
            println!("Application exiting");
        }
        _ => {}
    }
});
```

### Pattern 6: Exit and Restart

```rust
// Exit from a command
#[tauri::command]
fn quit_app(app: tauri::AppHandle) {
    app.exit(0); // 0 = success exit code
}

// Restart (requires tauri-plugin-process)
#[tauri::command]
fn restart_app(app: tauri::AppHandle) {
    app.restart();
}
```

```typescript
// Frontend exit/restart (requires @tauri-apps/plugin-process)
import { exit, relaunch } from '@tauri-apps/plugin-process';

await exit(0);
await relaunch();
```

---

## Async Runtime

Tauri 2.x uses **tokio** as the async runtime. Async commands are automatically spawned on the tokio runtime -- you do NOT need to configure tokio yourself.

The `tauri::async_runtime` module provides direct access when needed:

```rust
tauri::async_runtime::spawn(async {
    // Runs on the tokio runtime
});
```

**ALWAYS** use `async` commands for I/O, network, or long-running operations. Sync commands run on the main thread and will freeze the UI if they block.

**ALWAYS** use `#[tauri::command(async)]` on a sync function if you want it to run on the async runtime instead of the main thread:

```rust
#[tauri::command(async)]
fn heavy_sync_computation(data: Vec<u8>) -> Vec<u8> {
    // Runs on tokio runtime, not main thread
    expensive_transform(data)
}
```

---

## WindowEvent Variants (Tauri 2.x)

| Variant | Fields | Description |
|---------|--------|-------------|
| `Resized` | `PhysicalSize<u32>` | Window client area resized |
| `Moved` | `PhysicalPosition<i32>` | Window position changed |
| `CloseRequested` | `api: CloseRequestApi` | Close requested; call `api.prevent_close()` to cancel |
| `Destroyed` | -- | Window has been destroyed |
| `Focused` | `bool` | `true` = gained focus, `false` = lost |
| `ScaleFactorChanged` | `scale_factor, new_inner_size` | DPI/display scale changed |
| `DragDrop` | `DragDropEvent` | File drag-and-drop event |
| `ThemeChanged` | `Theme` | System theme changed (not on Linux) |

---

## Reference Links

- [references/methods.md](references/methods.md) -- Builder methods, Manager trait methods, AppHandle methods
- [references/examples.md](references/examples.md) -- Lifecycle patterns, setup hook, background tasks
- [references/anti-patterns.md](references/anti-patterns.md) -- Lifecycle mistakes and how to avoid them

### Official Sources

- https://v2.tauri.app/develop/
- https://docs.rs/tauri/latest/tauri/struct.Builder.html
- https://docs.rs/tauri/latest/tauri/struct.AppHandle.html
- https://docs.rs/tauri/latest/tauri/trait.Manager.html

