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
endThe #[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:
- Extracts each argument either from the JSON
argsor from aCommandArgextractor (State,Window,Webview,AppHandle,Channel,Resource). - Calls the original function inside a
tokiotask if the function isasync fn. - Converts the return type via
IpcResponseso 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 bytauri-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 theprotocol-assetCargo feature.isolation://— serves the iframe shell whenapp.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
- Permission resolution mechanics: systems/acl-and-capabilities.
- The
Window/Webviewabstractions involved: crates/tauri, crates/tauri-runtime. - JS API surface: packages/api.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.