calling-frontend-from-tauri-rust

Calling Frontend from Tauri Rust

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "calling-frontend-from-tauri-rust" with this command: npx skills add dchuk/claude-code-tauri-skills/dchuk-claude-code-tauri-skills-calling-frontend-from-tauri-rust

Calling Frontend from Tauri Rust

Tauri provides three mechanisms for Rust to communicate with the frontend: the event system, channels, and JavaScript evaluation.

Event System Overview

The event system enables bi-directional communication between Rust and frontend. Best for small data transfers and multi-consumer patterns. Not designed for low latency or high throughput.

Required Imports

use tauri::{AppHandle, Emitter, Manager, Listener, EventTarget}; use serde::Serialize;

import { listen, once, emit } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

Emitting Events from Rust

Global Events (All Listeners)

Use AppHandle::emit() to broadcast to all listeners:

use tauri::{AppHandle, Emitter};

#[tauri::command] fn download(app: AppHandle, url: String) { app.emit("download-started", &url).unwrap(); for progress in [1, 15, 50, 80, 100] { app.emit("download-progress", progress).unwrap(); } app.emit("download-finished", &url).unwrap(); }

Webview-Specific Events

Target specific webviews with emit_to() :

use tauri::{AppHandle, Emitter};

#[tauri::command] fn login(app: AppHandle, user: String, password: String) { let authenticated = user == "tauri-apps" && password == "tauri"; let result = if authenticated { "loggedIn" } else { "invalidCredentials" }; app.emit_to("login", "login-result", result).unwrap(); }

Filtered Events (Multiple Webviews)

Use emit_filter() for conditional targeting:

use tauri::{AppHandle, Emitter, EventTarget};

#[tauri::command] fn open_file(app: AppHandle, path: std::path::PathBuf) { app.emit_filter("open-file", path, |target| match target { EventTarget::WebviewWindow { label } => label == "main" || label == "file-viewer", _ => false, }).unwrap(); }

Event Payloads

Custom payloads must implement Serialize and Clone :

use serde::Serialize; use tauri::{AppHandle, Emitter};

#[derive(Clone, Serialize)] #[serde(rename_all = "camelCase")] struct DownloadProgress { download_id: usize, chunk_length: usize, total_size: usize, }

#[tauri::command] fn download(app: AppHandle, url: String) { app.emit("download-progress", DownloadProgress { download_id: 1, chunk_length: 150, total_size: 1000, }).unwrap(); }

Listening in Frontend

Global Event Listeners

import { listen } from '@tauri-apps/api/event';

type DownloadStarted = { url: string; downloadId: number; contentLength: number; };

listen<DownloadStarted>('download-started', (event) => { console.log(downloading ${event.payload.contentLength} bytes from ${event.payload.url}); });

Webview-Specific Listeners

import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

const appWebview = getCurrentWebviewWindow(); appWebview.listen<string>('logged-in', (event) => { localStorage.setItem('session-token', event.payload); });

Managing Listeners

import { listen, once } from '@tauri-apps/api/event';

// Unlisten to prevent memory leaks const unlisten = await listen('download-started', (event) => { console.log('download started'); }); unlisten(); // Stop listening when done

// Listen once for one-time events once('app-ready', (event) => { console.log('App is ready:', event.payload); });

Listening in Rust

Global and Webview Listeners

use tauri::{Listener, Manager};

tauri::Builder::default() .setup(|app| { // Global listener app.listen("download-started", |event| { println!("event received: {}", event.payload()); });

    // Webview-specific listener
    let webview = app.get_webview_window("main").unwrap();
    webview.listen("logged-in", |event| {
        println!("User logged in: {}", event.payload());
    });
    Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")

Unlisten and Listen Once

use tauri::Listener;

// Store event ID to unlisten later let event_id = app.listen("download-started", |event| { println!("download started"); }); app.unlisten(event_id);

// Conditional unlisten let handle = app.handle().clone(); app.listen("status-changed", move |event| { if event.payload() == ""ready"" { handle.unlisten(event.id()); } });

// Listen once app.once("ready", |event| { println!("app is ready: {}", event.payload()); });

Channels (High-Throughput Streaming)

For better performance than events, use channels:

Rust Channel Setup

use tauri::{AppHandle, ipc::Channel}; use serde::Serialize;

#[derive(Clone, Serialize)] #[serde(rename_all = "camelCase", tag = "event", content = "data")] enum DownloadEvent<'a> { #[serde(rename_all = "camelCase")] Started { url: &'a str, download_id: usize, content_length: usize }, #[serde(rename_all = "camelCase")] Progress { download_id: usize, chunk_length: usize }, #[serde(rename_all = "camelCase")] Finished { download_id: usize }, }

#[tauri::command] fn download(app: AppHandle, url: String, on_event: Channel<DownloadEvent>) { on_event.send(DownloadEvent::Started { url: &url, download_id: 1, content_length: 1000, }).unwrap();

for _ in 0..10 {
    on_event.send(DownloadEvent::Progress {
        download_id: 1,
        chunk_length: 100,
    }).unwrap();
}

on_event.send(DownloadEvent::Finished { download_id: 1 }).unwrap();

}

Frontend Channel Usage

import { invoke, Channel } from '@tauri-apps/api/core';

type DownloadEvent = | { event: 'started'; data: { url: string; downloadId: number; contentLength: number } } | { event: 'progress'; data: { downloadId: number; chunkLength: number } } | { event: 'finished'; data: { downloadId: number } };

const onEvent = new Channel<DownloadEvent>();

onEvent.onmessage = (message) => { switch (message.event) { case 'started': console.log(Download started: ${message.data.url}); break; case 'progress': console.log(Progress: ${message.data.chunkLength} bytes); break; case 'finished': console.log('Download complete!'); break; } };

await invoke('download', { url: 'https://example.com/file.json', onEvent });

JavaScript Evaluation

Execute JavaScript directly from Rust:

Basic Evaluation

use tauri::Manager;

tauri::Builder::default() .setup(|app| { let webview = app.get_webview_window("main").unwrap(); webview.eval("console.log('hello from Rust')")?; Ok(()) })

Evaluation with Data

use tauri::Manager;

#[tauri::command] fn notify_frontend(app: tauri::AppHandle, message: String) { if let Some(webview) = app.get_webview_window("main") { let script = format!("window.showNotification('{}')", message); webview.eval(&script).unwrap(); } }

Complex Data with serialize-to-javascript

Cargo.toml

[dependencies] serialize-to-javascript = "0.1"

use serialize_to_javascript::Serialized; use tauri::Manager;

#[derive(serde::Serialize)] struct AppState { user: String, logged_in: bool }

#[tauri::command] fn sync_state(app: tauri::AppHandle) { let state = AppState { user: "john".to_string(), logged_in: true }; if let Some(webview) = app.get_webview_window("main") { let serialized = Serialized::new(&state, &Default::default()).into_string(); webview.eval(&format!("window.updateState({})", serialized)).unwrap(); } }

Choosing the Right Method

Method Use Case Performance

Events (emit ) Multi-consumer, broadcast Moderate

Channels High-throughput streaming, single consumer High

JS Eval Direct DOM manipulation, no response needed Low overhead

Events: Notifying multiple windows, loose coupling, simple status updates.

Channels: File downloads/uploads with progress, real-time streaming, high-frequency updates.

JS Eval: One-off DOM updates, triggering frontend functions directly.

Complete Example: File Watcher

Rust Side

use tauri::{AppHandle, Emitter}; use serde::Serialize; use std::path::PathBuf;

#[derive(Clone, Serialize)] #[serde(rename_all = "camelCase")] struct FileChange { path: String, event_type: String }

#[tauri::command] fn watch_directory(app: AppHandle, path: PathBuf) { std::thread::spawn(move || { loop { app.emit("file-changed", FileChange { path: path.to_string_lossy().to_string(), event_type: "modified".to_string(), }).unwrap(); std::thread::sleep(std::time::Duration::from_secs(5)); } }); }

Frontend Side

import { listen } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core';

type FileChange = { path: string; eventType: string };

await invoke('watch_directory', { path: '/some/directory' });

const unlisten = await listen<FileChange>('file-changed', (event) => { console.log(File ${event.payload.eventType}: ${event.payload.path}); });

// Cleanup when component unmounts: unlisten();

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

integrating-tauri-js-frontends

No summary provided by upstream source.

Repository SourceNeeds Review
139-dchuk
Coding

configuring-tauri-permissions

No summary provided by upstream source.

Repository SourceNeeds Review
117-dchuk
Coding

understanding-tauri-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

calling-rust-from-tauri-frontend

No summary provided by upstream source.

Repository SourceNeeds Review