new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Tauri · Develop · all subjects

ipc/commands

98 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Commands use invoke API similar to fetch

The Commands API provides a primary method called invoke that is similar to the browser's fetch API. It allows the Frontend to invoke Rust functions, pass arguments, and receive data responses.

Commands JSON serialization requirement

Commands use a JSON-RPC like protocol under the hood to serialize requests and responses. All arguments passed to commands and all return data must be serializable to JSON.

Adding commands to plugins

Commands are defined in `commands.rs` as regular Tauri application commands. They can access AppHandle, Window, state, and take input parameters. To expose commands to the webview, hook them into the `invoke_handler()` call in `lib.rs` using `Builder::invoke_handler(tauri::generate_handler![commands::function_name])`. Define binding functions in `webview-src/index.ts` for JavaScript users.

Command example with Channel for progress reporting

The following shows a plugin command receiving AppHandle, Window, a Channel for progress, and a URL string: ```rust use tauri::{command, ipc::Channel, AppHandle, Runtime, Window}; #[command] async fn upload<R: Runtime>(app: AppHandle<R>, window: Window<R>, on_progress: Channel, url: String) { // implement command logic here on_progress.send(100).unwrap(); } ```

JavaScript binding function example for plugin command

Define binding functions in `webview-src/index.ts` for JavaScript users to call plugin commands: ```js import { invoke, Channel } from '@tauri-apps/api/core' export async function upload(url: string, onProgressHandler: (progress: number) => void): Promise<void> { const onProgress = new Channel<number>() onProgress.onmessage = onProgressHandler await invoke('plugin:<plugin-name>|upload', { url, onProgress }) } ``` Build the TypeScript code before testing.

Command with AppHandle and Window dependency injection

Plugin commands use dependency injection to access AppHandle and Window instances. Tauri provides these via type annotations. Commands can also access state and take input parameters through function parameters.

Plugin invocation command format

Plugin commands are invoked from JavaScript using the format `plugin:<plugin-name>|command_name`. For example, `invoke('plugin:<plugin-name>|upload', { url, onProgress })`.

Android command example

Example of an Android plugin command: ```kotlin import android.app.Activity import app.tauri.annotation.Command import app.tauri.annotation.TauriPlugin @TauriPlugin class ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { val ret = JSObject() ret.put("path", "/path/to/photo.jpg") invoke.resolve(ret) } } ```

iOS command example

Example of an iOS plugin command: ```swift class ExamplePlugin: Plugin { @objc public func openCamera(_ invoke: Invoke) throws { invoke.resolve(["path": "/path/to/photo.jpg"]) } } ```

Android suspend function with coroutine

To use a Kotlin suspend function in an Android plugin command, create a custom coroutine scope and launch the suspend function from within the command. Example: ```kotlin val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) @TauriPlugin class ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { scope.launch { openCameraInner(invoke) } } private suspend fun openCameraInner(invoke: Invoke) { val ret = JSObject() ret.put("path", "/path/to/photo.jpg") invoke.resolve(ret) } } ```

Call mobile command from Rust

Use tauri::plugin::PluginHandle to call a mobile command from Rust via the run_mobile_plugin method. Example: ```rust use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tauri::Runtime; #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CameraRequest { quality: usize, allow_edit: bool, } #[derive(Deserialize)] pub struct Photo { path: PathBuf, } impl<R: Runtime> <plugin-name;pascal-case><R> { pub fn open_camera(&self, payload: CameraRequest) -> crate::Result<Photo> { self .0 .run_mobile_plugin("openCamera", payload) .map_err(Into::into) } } ```

Android command arguments with InvokeArg

Android command arguments are defined as classes annotated with @app.tauri.annotation.InvokeArg. Arguments are parsed in a command with invoke.parseArgs(ClassName::class.java). Optional arguments are defined as var <name>: Type? = null. Arguments with defaults are var <name>: Type = <value>. Required arguments are lateinit var <name>: Type. Inner objects must also be annotated with @InvokeArg.

Android command argument parsing example

Example of Android command argument definitions and parsing: ```kotlin import android.app.Activity import android.webkit.WebView import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin @InvokeArg internal class OpenAppArgs { lateinit var name: String var timeout: Int? = null } @InvokeArg internal class OpenArgs { lateinit var requiredArg: String var allowEdit: Boolean = false var quality: Int = 100 var app: OpenAppArgs? = null } @TauriPlugin class ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { val args = invoke.parseArgs(OpenArgs::class.java) } } ```

iOS command arguments with Decodable

iOS command arguments are defined as classes inheriting Decodable. Arguments are parsed in a command with try invoke.parseArgs(ClassName.self). Optional arguments are defined as var <name>: Type?. Required arguments are defined as let <name>: Type. Arguments with default values are not supported; use a nullable type instead and set the default in the command function. Inner objects must also inherit Decodable.

iOS command argument parsing example

Example of iOS command argument definitions and parsing: ```swift class OpenAppArgs: Decodable { let name: String var timeout: Int? } class OpenArgs: Decodable { let requiredArg: String var allowEdit: Bool? var quality: UInt8? var app: OpenAppArgs? } class ExamplePlugin: Plugin { @objc public func openCamera(_ invoke: Invoke) throws { let args = try invoke.parseArgs(OpenArgs.self) invoke.resolve(["path": "/path/to/photo.jpg"]) } } ```

Android JNI native function call from Kotlin

To call Rust code from Android using JNI, load the native library in the plugin init block and declare external functions in Kotlin. The function format is Java_package_class_method. Example: ```kotlin private const val TAG = "MyPlugin" init { try { System.loadLibrary("app_lib") Log.d(TAG, "Successfully loaded libapp_lib.so") } catch (e: UnsatisfiedLinkError) { Log.e(TAG, "Failed to load libapp_lib.so", e) throw e } } external fun helloWorld(name: String): String? ```

Android JNI Rust function definition

Rust JNI function for Android must be no_mangle and use extern "system". The function name format is Java_package_class_method. Example: ```rust #[cfg(target_os = "android")] #[no_mangle] pub extern "system" fn Java_com_example_HelloWorld_helloWorld( mut env: JNIEnv, _class: JClass, name: JString, ) -> jstring { log::debug!("Calling JNI Hello World!"); let result = format!("Hello, {}!", name); match env.new_string(result) { Ok(jstr) => jstr.into_raw(), Err(e) => { log::error!("Failed to create JString: {}", e); std::ptr::null_mut() } } } ``` Add jni = "0.21" to Cargo.toml under [target.'cfg(target_os = "android")'.dependencies].

iOS FFI Swift to Rust call

To call Rust code from iOS using FFI, define hooks in Swift with @_silgen_name annotation and implement cleanup functions. Example: ```swift @_silgen_name("hello_world_ffi") private static func helloWorldFFI(_ name: UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>? @_silgen_name("free_hello_result_ffi") private static func freeHelloResult(_ result: UnsafeMutablePointer<CChar>) static func helloWorld(name: String) -> String? { let resultPtr = name.withCString({ helloWorldFFI($0) }) let result = String(cString: resultPtr) freeHelloResult(resultPtr) return result } ```

iOS FFI Rust function definition

Rust FFI functions for iOS must be no_mangle and use extern "C". Function names must match the @_silgen_name annotations on the Swift side. Example: ```rust #[no_mangle] pub unsafe extern "C" fn hello_world_ffi(c_name: *const c_char) -> *mut c_char { let name = match CStr::from_ptr(c_name).to_str() { Ok(s) => s, Err(e) => { log::error!("[iOS FFI] Failed to convert C string: {}", e); return std::ptr::null_mut(); } }; let result = format!("Hello, {}!", name); match CString::new(result) { Ok(c_str) => c_str.into_raw(), Err(e) => { log::error!("[iOS FFI] Failed to create C string: {}", e); std::ptr::null_mut() } } } #[no_mangle] pub unsafe extern "C" fn free_hello_result_ffi(result: *mut c_char) { if !result.is_null() { drop(CString::from_raw(result)); } } ```

JavaScript invoke requestPermissions command

Call requestPermissions from JavaScript: ```javascript import { invoke, PermissionState } from '@tauri-apps/api/core' interface Permissions { postNotification: PermissionState } const state = await invoke<Permissions>('plugin:<plugin-name>|requestPermissions', { permissions: ['postNotification'] }) ```

Rust invoke checkPermissions and requestPermissions

Call plugin permission commands from Rust: ```rust use serde::{Serialize, Deserialize}; use tauri::{plugin::PermissionState, Runtime}; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct PermissionResponse { pub post_notification: PermissionState, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct RequestPermission { post_notification: bool, } impl<R: Runtime> Notification<R> { pub fn request_post_notification_permission(&self) -> crate::Result<PermissionState> { self.0 .run_mobile_plugin::<PermissionResponse>("requestPermissions", RequestPermission { post_notification: true }) .map(|r| r.post_notification) .map_err(Into::into) } pub fn check_permissions(&self) -> crate::Result<PermissionResponse> { self.0 .run_mobile_plugin::<PermissionResponse>("checkPermissions", ()) .map_err(Into::into) } } ```

Commands enable type-safe Rust calls from frontend

Tauri provides a command system for calling Rust functions from your web app with type safety. Commands can accept arguments, return values, be async, and return errors.

Define commands with #[tauri::command] macro

Commands are Rust functions annotated with #[tauri::command]. In lib.rs, commands cannot be marked as pub due to glue code generation limitations. In separate modules they must be marked pub. Command names must be unique across all modules.

Register commands with tauri::generate_handler! macro

Commands must be registered in the builder using tauri::generate_handler![command_name1, command_name2]. The invoke_handler method can only be called once; multiple calls will only use the last one. Pass all commands to a single generate_handler! call.

Invoke commands from JavaScript

Use invoke() from @tauri-apps/api/core to call commands from JavaScript. The function returns a promise. Example: invoke('my_custom_command', { arg: 'value' }).then(result => console.log(result))

Command arguments use camelCase in JavaScript

Arguments passed to commands from JavaScript use camelCase keys by default. You can override this with #[tauri::command(rename_all = "snake_case")] to accept snake_case arguments instead.

Command arguments must implement serde::Deserialize

Command handler arguments can be of any type that implements serde::Deserialize. Arguments are passed as a JSON object from JavaScript.

Command return values must implement serde::Serialize

Command handlers can return data of any type that implements serde::Serialize. The invoke promise resolves with the returned value. Return values are serialized to JSON before sending to frontend.

Command error handling with Result

Commands can return Result<SuccessType, ErrorType> to handle errors. If the command returns an error, the invoke promise rejects; otherwise it resolves. Error types must implement serde::Serialize.

Convert Rust errors to String for simple error handling

For simple error handling, use map_err to convert std library errors to String: Result<(), String>. Example: std::fs::File::open("path").map_err(|err| err.to_string())?

Async commands run on separate task pool

Async commands are executed on a separate async task using async_runtime::spawn and do not block the main thread. Declare a command as async by using the async keyword: #[tauri::command] async fn my_command() {}

Async commands cannot use borrowed arguments

Async command functions cannot include borrowed arguments like &str or State<'_, Data> in the signature. Use Option 1: convert to owned types like String, or Option 2: wrap the return type in Result to bypass borrowing issues.

Workaround borrowed types in async commands

Two options to use borrowed types in async commands: Option 1 - convert &str to String in the function signature. Option 2 - use Result<ReturnType, ErrorType> as the return type, for example Result<String, ()>. The return value must then be wrapped in Ok().

Invoke async commands from JavaScript

Async commands are invoked the same way as synchronous commands from JavaScript. The invoke() function already returns a promise, so it works identically: invoke('my_async_command').then(() => console.log('Completed!'))

Generic Runtime parameter for AppHandle and WebviewWindow

AppHandle and WebviewWindow take a generic parameter R: Runtime. When the wry feature is enabled (default), the runtime defaults to Wry. For other runtimes like mock runtime, explicitly declare the generic: async fn my_command<R: Runtime>(app_handle: AppHandle<R>, webview_window: WebviewWindow<R>)

Invoke with raw request body from JavaScript

Pass raw binary data to a command by providing an ArrayBuffer or Uint8Array as the payload argument to invoke(), and include request headers in the third argument: const data = new Uint8Array([1, 2, 3]); await __TAURI__.core.invoke('upload', data, { headers: { Authorization: 'apikey' } });

WASM frontend must adapt invoke for arguments

When using a Rust WASM frontend calling invoke() without arguments, declare two separate functions with #[wasm_bindgen]: one for invoke without arguments (js_name = invoke) and one for invoke with arguments. They must have different names due to Rust not supporting optional arguments.

Access state in commands

State can be accessed directly in command functions by declaring a State parameter with the correct type: ```rust #[tauri::command] fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 { let mut state = state.lock().unwrap(); state.counter += 1; state.counter } ```

Tauri 2.0: Migrate to Core module

The `@tauri-apps/api/tauri` module was renamed to `@tauri-apps/api/core`. Update imports: `import { invoke } from "@tauri-apps/api/core"` instead of `import { invoke } from "@tauri-apps/api/tauri"`.

JS Map serialization fix in invoke in Tauri 1.3

Tauri 1.3 fixes serialization of JavaScript Map objects when used in invoke calls to the Rust backend.

Android plugin structure with @TauriPlugin annotation

An Android plugin for Tauri uses the @TauriPlugin annotation on a class extending Plugin. Commands are defined with the @Command annotation. The example ExamplePlugin has a ping method that receives an Invoke object, extracts a string value parameter with invoke.getString("value"), creates a JSObject response, puts the value into it, and resolves with invoke.resolve(ret).

iOS plugin structure with Plugin inheritance

An iOS plugin for Tauri inherits from Plugin and uses @objc public func for command methods. The example ping method receives an Invoke object, extracts a string value with invoke.getString("value"), and resolves with invoke.resolve(["value": value as Any]). A separate function with @_cdecl("init_plugin_example") must be defined to register the plugin using Tauri.registerPlugin(webview: webview, name: name.toString(), plugin: ExamplePlugin()).

Rust plugin initialization for iOS and Android

A Rust plugin uses Builder::new(name).setup() to initialize. For iOS, use the ios_plugin_binding! macro with the C function name and call api.register_ios_plugin(init_plugin_example)?. For Android, call api.register_android_plugin("package.name", "ClassName")? within #[cfg(target_os = "...")] blocks.

Frontend invocation of plugin commands

To call a plugin command from the frontend, use invoke('plugin:pluginName|commandName', { parameters }) from @tauri-apps/api/tauri. The example invokes 'plugin:example|ping' with a value parameter and logs the response.

tauri::ipc module and command module move

Tauri 2.0 moved items from tauri::command module to tauri::ipc module so the import name does not clash with the tauri::command macro.

Rust Manager API changes in v2

In Tauri v2: `App::get_cli_matches` removed, use `tauri-plugin-cli`. `App::global_shortcut_manager` and `AppHandle::global_shortcut_manager` removed, use `tauri-plugin-global-shortcut`. `Manager::fs_scope` removed, access filesystem scope via `tauri_plugin_fs::FsExt`.

Rust plugin API changes in v2

`Plugin::PluginApi` now receives plugin configuration as a second argument. `Plugin::setup_with_config` removed, use the updated `tauri::Plugin::PluginApi` instead. `scope::ipc::RemoteDomainAccessScope::enable_tauri_api` and `enables_tauri_api` removed, enable each core plugin individually using `scope::ipc::RemoteDomainAccessScope::add_plugin` instead.

Rust scope and process module changes in v2

`scope::IpcScope` module removed, use `scope::ipc::Scope` instead. `scope::FsScope`, `scope::GlobPattern`, and `scope::FsScopeEvent` removed, use `scope::fs::Scope`, `scope::fs::Pattern`, and `scope::fs::Event` respectively. `api::process::current_binary` and `tauri::api::process::restart` moved to `tauri::process`. `updater` module removed, use `tauri-plugin-updater`.

JavaScript API module removals and migrations in v2

The `@tauri-apps/api` package no longer provides non-core modules. Only previously core modules (`tauri` now `core`), `path`, `event`, and `window` are exported. All others moved to plugins: `@tauri-apps/api/tauri` renamed to `@tauri-apps/api/core`. `@tauri-apps/api/cli` removed, use `@tauri-apps/plugin-cli`. `@tauri-apps/api/clipboard` removed, use `@tauri-apps/plugin-clipboard`. `@tauri-apps/api/dialog` removed, use `@tauri-apps/plugin-dialog`. `@tauri-apps/api/fs` removed, use `@tauri-apps/plugin-fs`. `@tauri-apps/api/global-shortcut` removed, use `@tauri-apps/plugin-global-shortcut`. `@tauri-apps/api/http` removed, use `@tauri-apps/plugin-http`. `@tauri-apps/api/os` removed, use `@tauri-apps/plugin-os`. `@tauri-apps/api/notification` removed, use `@tauri-apps/plugin-notification`. `@tauri-apps/api/process` removed, use `@tauri-apps/plugin-process`. `@tauri-apps/api/shell` removed, use `@tauri-apps/plugin-shell`. `@tauri-apps/api/updater` removed, use `@tauri-apps/plugin-updater`. `@tauri-apps/api/window` renamed to `@tauri-apps/api/webviewWindow`. V1 plugins now published as `@tauri-apps/plugin-<plugin-name>` instead of git-sourced `tauri-plugin-<plugin-name>-api`.

Migrate JavaScript core module import

Rename the import from `@tauri-apps/api/tauri` to `@tauri-apps/api/core`. Example: change `import { invoke } from "@tauri-apps/api/tauri"` to `import { invoke } from "@tauri-apps/api/core"`.

Migrate CLI plugin: JavaScript setup

To migrate CLI functionality to JavaScript, add `@tauri-apps/plugin-cli` to package.json, initialize the plugin in Rust with `tauri_plugin_cli::init()`, then import and use `import { getMatches } from '@tauri-apps/plugin-cli'` and call `const matches = await getMatches()`.

Migrate Clipboard plugin: Rust setup

Add `tauri-plugin-clipboard-manager = "2"` to Cargo.toml. Initialize with `.plugin(tauri_plugin_clipboard_manager::init())` in builder. In setup, use `app.clipboard().write()` and `app.clipboard().read()` with the `ClipboardExt` trait.

Migrate Clipboard plugin: JavaScript setup

Add `@tauri-apps/plugin-clipboard-manager` to package.json. Initialize in Rust with `tauri_plugin_clipboard_manager::init()`. Use JavaScript: `import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager'` and call methods like `await writeText('text')` and `await readText()`.

Migrate Dialog plugin: Rust setup

Add `tauri-plugin-dialog = "2"` to Cargo.toml. Initialize with `.plugin(tauri_plugin_dialog::init())`. In setup, use `app.dialog().file().pick_file()` or `app.dialog().message().show()` with the `DialogExt` trait.

Migrate Dialog plugin: JavaScript setup

Add `@tauri-apps/plugin-dialog` to package.json. Initialize in Rust with `tauri_plugin_dialog::init()`. Use JavaScript: `import { save } from '@tauri-apps/plugin-dialog'` and call methods like `const filePath = await save({ filters: [...] })`.

Migrate Filesystem plugin: Rust uses std::fs

For Rust, the filesystem API `@tauri-apps/api/fs` has been removed. Use Rust's standard library `std::fs` instead of Tauri filesystem APIs.

Migrate Filesystem plugin: JavaScript setup and API changes

Add `tauri-plugin-fs = "2"` to Cargo.toml and `@tauri-apps/plugin-fs` to package.json. Initialize with `.plugin(tauri_plugin_fs::init())`. API changes: removed `Dir` enum alias (use `BaseDirectory`), removed `FileEntry`, `FsBinaryFileOption`, `FsDirOptions`, `FsOptions`, `FsTextFileOption`, `BinaryFileContents` interfaces, renamed `createDir` to `mkdir`, renamed `readBinaryFile` to `readFile`, replaced `removeDir` and `removeFile` with `remove`, replaced `renameFile` with `rename`, renamed `writeBinaryFile` to `writeFile`.

Migrate Filesystem plugin: JavaScript example

JavaScript example: `import { mkdir, BaseDirectory } from '@tauri-apps/plugin-fs'; await mkdir('db', { baseDir: BaseDirectory.AppLocalData });`

Migrate Global Shortcut plugin: Rust setup

Add `tauri-plugin-global-shortcut = "2"` to Cargo.toml with target condition for non-mobile. Initialize with `.plugin(tauri_plugin_global_shortcut::Builder::default().build())`. In setup, use `app.global_shortcut().register()` with the `GlobalShortcutExt` trait.

Migrate Global Shortcut plugin: JavaScript setup

Add `@tauri-apps/plugin-global-shortcut` to package.json. Initialize in Rust with `tauri_plugin_global_shortcut::Builder::default().build()`. Use JavaScript: `import { register } from '@tauri-apps/plugin-global-shortcut'` and call `await register('CommandOrControl+Shift+C', () => { ... })`.

Give your agent this brain