# How to Implement Custom Commands in Tauri: Exposing Rust Functions to the Frontend

> Learn how to implement custom commands in Tauri. Expose your Rust functions to the frontend efficiently using the invoke API and tauri::generate_handler!. Build powerful desktop apps.

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

---

**To implement custom commands in Tauri, annotate Rust functions with `#[command]`, register them via `tauri::generate_handler!`, and call them from the frontend using the `invoke` API.**

Tauri commands provide a type-safe bridge between your Rust backend and JavaScript frontend. This guide explains how the `tauri-apps/tauri` repository implements the command system and how you can leverage it to expose native functionality to your web-based UI.

## What Are Tauri Custom Commands?

A **Tauri command** is a Rust function annotated with the `#[command]` attribute that becomes callable from JavaScript. At compile time, Tauri's procedural macros expand these functions into wrappers that handle serialization, IPC communication, and error propagation automatically. The system supports synchronous and asynchronous functions, state injection, and custom error types.

## The Architecture of Tauri Commands

Understanding the internal flow helps debug issues and leverage advanced features. The command system consists of four distinct layers working together.

### The `#[command]` Macro Expansion

When you annotate a function with `#[command]`, the `tauri-macros` crate generates a wrapper function prefixed with `__cmd__`. According to the source code in [`crates/tauri-macros/src/command/handler.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/handler.rs) (lines 32-69), the `Handler::parse` method processes command definitions, formats wrapper identifiers using `format_command_wrapper`, and builds a dispatch map. The generated wrapper handles JSON deserialization of arguments and serialization of return values.

### The `generate_handler!` Dispatcher

The `tauri::generate_handler!` macro expands into a match-statement that dispatches incoming IPC messages to the appropriate wrapper. In [`examples/commands/main.rs`](https://github.com/tauri-apps/tauri/blob/main/examples/commands/main.rs) (lines 26-33), the macro collects command paths and creates a closure that matches `invoke.message.command()` against registered command names. This dispatcher is plugged into the Tauri builder via `.invoke_handler(...)`.

### Frontend Invocation

The JavaScript frontend calls commands through `window.__TAURI__.invoke()` or the higher-level `invoke` function from `@tauri-apps/api`. The payload serializes to JSON and travels over Tauri's IPC channel to the Rust runtime, which routes it to the generated dispatcher.

## Step-by-Step Implementation Guide

Follow these steps to expose Rust functions to your frontend.

### 1. Create a Basic Command

Define a Rust function in your [`src-tauri/src/main.rs`](https://github.com/tauri-apps/tauri/blob/main/src-tauri/src/main.rs) or a dedicated module. Apply the `#[command]` attribute and choose a descriptive name.

```rust
use tauri::command;

#[command]
fn greet(name: String) -> String {
    format!("Hello, {name}!")
}

```

The function can accept any serializable type and return `String`, `i32`, custom structs, or `Result<T, E>`.

### 2. Register the Command Handler

In your main function, use `tauri::generate_handler!` to register the command with the Tauri builder.

```rust
fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

This macro expansion, detailed in [`crates/tauri-macros/src/command/handler.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/handler.rs), creates the dispatch logic that bridges IPC messages to your `greet` function.

### 3. Call from the Frontend

Install `@tauri-apps/api` in your frontend project, then import and call the command.

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

async function sayHello() {
  const reply = await invoke("greet", { name: "World" });
  console.log(reply); // → "Hello, World!"
}

sayHello();

```

The first argument matches the Rust function name exactly. The second argument is an object containing the parameters defined in your Rust signature.

## Advanced Command Patterns

The Tauri command system supports sophisticated patterns for production applications.

### Asynchronous Commands

Mark functions as `async` to perform non-blocking I/O operations. The generated wrapper automatically handles the async runtime integration.

```rust
#[command]
async fn fetch_data(url: String) -> Result<String, String> {
    let response = reqwest::get(&url).await
        .map_err(|e| e.to_string())?;
    response.text().await
        .map_err(|e| e.to_string())
}

```

### State Injection

Access shared application state using the `State<'_, T>` extractor. First, manage the state in your builder:

```rust
#[derive(Debug)]
pub struct AppState {
    counter: std::sync::atomic::AtomicU64,
}

fn main() {
    tauri::Builder::default()
        .manage(AppState { 
            counter: std::sync::atomic::AtomicU64::new(0) 
        })
        .invoke_handler(tauri::generate_handler![increment])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

Then access it in your command:

```rust
use tauri::State;
use std::sync::atomic::Ordering;

#[command]
fn increment(state: State<'_, AppState>) -> u64 {
    state.counter.fetch_add(1, Ordering::Relaxed) + 1
}

```

As shown in [`examples/commands/main.rs`](https://github.com/tauri-apps/tauri/blob/main/examples/commands/main.rs) (lines 66-86), stateful commands work with both sync and async functions.

### Window Access

Access the current window instance to perform window-specific operations:

```rust
use tauri::Window;

#[command]
fn window_label(window: Window) {
    println!("Current window: {}", window.label());
}

```

This pattern appears in [`examples/commands/main.rs`](https://github.com/tauri-apps/tauri/blob/main/examples/commands/main.rs) (lines 31-35), demonstrating how the framework injects the `Window` object automatically.

### Error Handling

Return `Result<T, E>` where `E` implements `std::error::Error` to propagate errors to JavaScript as rejected Promises.

```rust
use thiserror::Error;

#[derive(Error, Debug)]
enum ValidationError {
    #[error("input cannot be empty")]
    EmptyInput,
    #[error("input too long")]
    TooLong,
}

#[command]
fn validate_input(data: String) -> Result<String, ValidationError> {
    if data.is_empty() {
        Err(ValidationError::EmptyInput)
    } else if data.len() > 100 {
        Err(ValidationError::TooLong)
    } else {
        Ok(data)
    }
}

```

When the error variant triggers, the JavaScript Promise rejects with the error message string.

## Summary

- **Annotate** Rust functions with `#[command]` to expose them to the frontend; the macro in [`crates/tauri-macros/src/command/mod.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/mod.rs) generates `__cmd__` wrappers automatically.
- **Register** commands using `tauri::generate_handler!` in your main function to build the dispatch table defined in [`crates/tauri-macros/src/command/handler.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/handler.rs).
- **Call** commands from JavaScript using `invoke("command_name", { args })` from `@tauri-apps/api`.
- **Inject** framework types like `Window` and `State<'_, T>` to access runtime resources without manual initialization.
- **Handle** errors by returning `Result<T, E>`; Tauri serializes errors into JavaScript Promise rejections.

## Frequently Asked Questions

### How does Tauri serialize data between JavaScript and Rust?

Tauri uses **JSON serialization** for all IPC communication. The generated command wrappers in [`crates/tauri-macros/src/command/wrapper.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/wrapper.rs) automatically serialize JavaScript arguments into Rust types using `serde`, and serialize Rust return values back to JSON for the frontend. Complex types must implement `Serialize` and `Deserialize` from the `serde` crate.

### Can I use custom types as command arguments?

Yes, provided they implement `serde::Deserialize`. Define your struct in Rust and mirror its structure in TypeScript for type safety.

```rust
#[derive(Deserialize)]
struct User {
    id: u64,
    name: String,
}

#[command]
fn create_user(user: User) -> String {
    format!("Created user {}", user.name)
}

```

### What is the performance cost of Tauri commands?

Commands have minimal overhead—**just JSON serialization and one IPC hop**. The wrapper generation happens at compile time, so runtime performance matches native Rust function calls once the data crosses the boundary. For high-frequency operations, batch calls or use Tauri's event system instead of individual commands.

### How do I protect commands from unauthorized access?

Tauri implements **Access Control Lists (ACL)** that filter unused commands based on the application manifest. As shown in [`crates/tauri-macros/src/command/handler.rs`](https://github.com/tauri-apps/tauri/blob/main/crates/tauri-macros/src/command/handler.rs) (line 44), the `filter_unused_commands` function removes commands not explicitly allowed in your [`tauri.conf.json`](https://github.com/tauri-apps/tauri/blob/main/tauri.conf.json). Additionally, validate all inputs and implement authentication checks inside your command functions before executing sensitive operations.