Open-Source Wikis

/

Tauri

/

Systems

/

IPC and commands

tauri-apps/tauri

IPC and commands

This page traces an IPC message from a JS invoke() call to the Rust function that handles it, and back. The mechanism applies equally to user commands and to plugin commands; the only difference is namespacing.

Wire format

A command call from JS sends a JSON object over the WRY IPC bridge:

{
  "cmd": "plugin:fs|read_text_file",
  "callback": 1234,
  "error": 1235,
  "args": { "path": "/etc/hosts" }
}

cmd is the command name. The plugin:<name>|<cmd> form denotes a plugin command; bare names are app-level commands. callback and error are integer slot ids the JS side allocated; the Rust response targets one of them. The Rust → JS path uses format_callback (crates/tauri/src/ipc/format_callback.rs) to write a JS expression that resolves the right promise.

Tauri also supports binary IPC for large payloads. WRY exposes raw Bytes — Tauri uses this for Channel<T> (crates/tauri/src/ipc/channel.rs) and for streaming responses such as the asset:// protocol.

End-to-end flow

sequenceDiagram
    participant JS as JS (@tauri-apps/api)
    participant Bridge as window.__TAURI_INTERNALS__
    participant Wry as tauri-runtime-wry IPC handler
    participant Mgr as AppManager (manager/mod.rs)
    participant Auth as RuntimeAuthority
    participant Cmd as #[command] wrapper

    JS->>Bridge: invoke("plugin:fs|read_text_file", { path })
    Bridge->>Wry: postMessage(JSON or binary)
    Wry->>Mgr: on_message(InvokeRequest)
    Mgr->>Auth: resolve_access(webview, "plugin:fs|read_text_file", args)
    alt allowed
        Auth-->>Mgr: AccessKey
        Mgr->>Cmd: Invocation::resolve(&self, args, key)
        Cmd-->>Mgr: serde_json::Value | Error
        Mgr->>Wry: format_callback(...)
        Wry-->>JS: window.__TAURI_internals_<id>(payload)
    else denied
        Auth-->>Mgr: Forbidden
        Mgr->>Wry: error response
        Wry-->>JS: rejection
    end

The #[command] macro

#[tauri::command] (from tauri-macros) wraps a user function:

#[tauri::command]
fn read_text_file(state: tauri::State<'_, FsState>, path: String) -> Result<String, MyErr> {}

It generates a wrapper that:

  1. Extracts each argument either from the JSON args or from a CommandArg extractor (State, Window, Webview, AppHandle, Channel, Resource).
  2. Calls the original function inside a tokio task if the function is async fn.
  3. Converts the return type via IpcResponse so it serialises into the JSON response or sends bytes through a channel.

The dispatch table is built by generate_handler!. It produces a closure Box<dyn for<'r> Fn(Invoke<'r, R>) -> bool + Send + Sync> that the Builder::invoke_handler consumes (crates/tauri/src/app.rs).

Argument extractors live in crates/tauri/src/ipc/command.rs. Adding a new one means implementing CommandArg + updating the tauri-macros recogniser.

RuntimeAuthority

crates/tauri/src/ipc/authority.rs (~34 KB) implements ACL enforcement. Inputs:

  • The resolved ACL (tauri_utils::acl::Resolved) embedded at compile time by tauri-codegen.
  • The webview's identity (its label and origin URL).
  • The command name and the deserialised arguments.

The RuntimeAuthority walks the matching capability set, verifies the command is allowed, applies any scope values (e.g. fs allow-paths) to the request, and returns either a ResolvedCommand accept or a structured rejection. The error message intentionally tells the developer which capability they are missing.

Manager::add_capability (gated by dynamic-acl) lets app code add capabilities at runtime; this writes through the same authority object.

Channels

crates/tauri/src/ipc/channel.rs provides Channel<T>, a long-lived Rust → JS stream. The Rust side gets a Channel<T> extractor in a command, calls channel.send(value) repeatedly, and the JS side consumes:

const channel = new Channel<Progress>();
channel.onmessage = (p) => updateUI(p);
await invoke('download', { url, channel });

Internally each message is sent through the WRY postMessage path under the __TAURI_CHANNEL__ namespace and routed back to the registered onmessage handler.

format_callback

crates/tauri/src/ipc/format_callback.rs (~12 KB) builds the JS code string Tauri evals to resolve a promise. It guards against script injection (it JSON.stringify's the payload and validates the callback id) and emits the right global symbol for either success or error.

Custom protocols (asset & isolation)

crates/tauri/src/protocol/ registers two custom URI schemes during webview construction (in crates/tauri-runtime-wry/src/lib.rs):

  • asset:// — reads files from disk under the configured asset scope. Range-supports for media. Gated by the protocol-asset Cargo feature.
  • isolation:// — serves the iframe shell when app.security.pattern.use = "isolation". The codegen embeds a fresh AES key into the shell so the outer frame can wrap/unwrap IPC payloads.

Both are subject to the ACL: a webview must hold the relevant scope to read a path.

Where things live

Concern File
#[command] macro expansion crates/tauri-macros/src/command/
generate_handler! macro crates/tauri-macros/src/command/handler.rs
Invoke type, CommandArg crates/tauri/src/ipc/command.rs
Dispatch entry point crates/tauri/src/ipc/mod.rs
ACL enforcement crates/tauri/src/ipc/authority.rs
Channel<T> crates/tauri/src/ipc/channel.rs
format_callback crates/tauri/src/ipc/format_callback.rs
WRY IPC bridge crates/tauri-runtime-wry/src/lib.rs
JS-side invoke packages/api/src/core.ts
JS-side Channel packages/api/src/core.ts

Cross-references

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

IPC and commands – Tauri wiki | Factory