DevToolBoxGRATIS
Blogg

Tauri Complete Guide: Lightweight Cross-Platform Desktop Apps with Rust (2026)

19 min readby DevToolBox Team

Tauri is an open-source framework for building lightweight, secure, cross-platform desktop applications using a Rust backend and any web frontend. Unlike Electron, Tauri does not bundle Chromium or Node.js, resulting in dramatically smaller binaries (often under 5 MB), lower memory usage, and a stronger security model. With Tauri 2.0, the framework extends beyond the desktop to support iOS and Android, making it a compelling choice for developers who want native performance and web development flexibility.

TL;DR

Tauri is a Rust-powered framework for building cross-platform desktop apps with web frontends (React, Vue, Svelte, SolidJS). It produces binaries under 5 MB, uses 50-80% less RAM than Electron, provides a granular permissions-based security model, and supports mobile platforms (iOS/Android) in Tauri 2.0. The Rust backend handles system APIs while the frontend renders in the OS native webview.

Key Takeaways
  • Tauri apps are dramatically smaller than Electron apps because they use the OS native webview instead of bundling Chromium, often producing binaries under 5 MB.
  • The Rust backend provides memory safety, high performance, and direct access to system APIs without garbage collection overhead.
  • Tauri 2.0 introduces mobile support for iOS and Android, allowing a single codebase to target desktop and mobile platforms.
  • A granular permissions system controls which system APIs the frontend can access, making Tauri apps more secure by default than Electron apps.
  • Tauri works with any web frontend framework including React, Vue, Svelte, SolidJS, and plain HTML/CSS/JS.
  • The plugin ecosystem provides ready-made modules for local storage, SQL databases, HTTP requests, file system access, shell commands, and auto-updates.

What Is Tauri and How Does It Work?

Tauri is a toolkit for building desktop applications where the user interface is built with web technologies (HTML, CSS, JavaScript) and the backend logic runs in Rust. Instead of shipping an entire browser engine like Electron does with Chromium, Tauri uses the webview already installed on the operating system: WebView2 on Windows, WebKit on macOS, and WebKitGTK on Linux.

This architecture means Tauri apps inherit the rendering engine of the host OS while the Rust core handles system-level operations like file access, window management, and inter-process communication. The result is applications that are an order of magnitude smaller and consume significantly less memory than their Electron equivalents.

// Tauri Architecture Overview
//
// +---------------------------+
// |     Web Frontend          |
// |  (React/Vue/Svelte/etc)   |
// |     HTML + CSS + JS       |
// +---------------------------+
//           | IPC (invoke / events)
// +---------------------------+
// |     Tauri Core (Rust)     |
// |  Commands, Plugins, State |
// |  Window Mgmt, FS, Shell   |
// +---------------------------+
//           | OS APIs
// +---------------------------+
// |   OS Native Webview       |
// |  WebView2 / WebKit /      |
// |  WebKitGTK                |
// +---------------------------+

Tauri vs Electron: A Detailed Comparison

The most common question developers ask is how Tauri compares to Electron. Here is a side-by-side breakdown of the key differences:

FeatureTauriElectron
Backend LanguageRustNode.js (JavaScript)
Rendering EngineOS native webviewBundled Chromium
Binary Size (Hello World)~3-5 MB~150-200 MB
Memory Usage~30-80 MB~150-300 MB
Security ModelGranular permissionsFull Node.js access
Mobile SupportiOS and Android (v2)Not supported
Cold Startup Time~0.5-1 second~2-5 seconds
Auto-UpdateBuilt-in pluginelectron-updater

What Is New in Tauri 2.0

Tauri 2.0 is a major release that restructures the framework around a plugin-based architecture and adds mobile platform support. The core has been rewritten to be more modular, and many features that were built-in to Tauri 1.x are now official plugins that you opt into.

Key changes include a new permissions system that replaces the allowlist from v1, first-class support for building iOS and Android apps from the same codebase, Swift and Kotlin bindings for platform-specific mobile code, a multiwebview API for complex window layouts, and significantly improved IPC performance.

Project Setup with create-tauri-app

The fastest way to create a new Tauri project is with the official scaffolding tool. It supports multiple frontend frameworks and package managers out of the box.

# Create a new Tauri project with the interactive CLI
npm create tauri-app@latest

# Or specify options directly
npm create tauri-app@latest my-app -- \
  --template react-ts

# Available templates:
# vanilla, vanilla-ts, react, react-ts,
# vue, vue-ts, svelte, svelte-ts,
# solid, solid-ts, angular, preact, preact-ts

# Project structure after scaffolding:
# my-app/
#   src/           <- frontend code (React/Vue/etc)
#   src-tauri/
#     src/
#       main.rs    <- Rust entry point
#       lib.rs     <- command definitions
#     Cargo.toml   <- Rust dependencies
#     tauri.conf.json  <- Tauri configuration
#     capabilities/    <- permissions config
#   package.json
# Development commands
cd my-app
npm install

# Start dev server with hot-reload
npm run tauri dev

# Build for production
npm run tauri build

# Add mobile targets
npm run tauri android init
npm run tauri ios init

# Run on mobile
npm run tauri android dev
npm run tauri ios dev

Rust Commands and the Invoke System

Tauri commands are Rust functions that the frontend can call through an IPC bridge. You define them in Rust with the #[tauri::command] attribute and invoke them from JavaScript using the invoke function. Commands can accept arguments, return values, and access application state.

Commands run on the Rust side, so they have full access to the file system, network, and any Rust crate. The IPC layer automatically serializes and deserializes arguments and return values using serde.

// src-tauri/src/lib.rs
use tauri::State;
use std::sync::Mutex;

// Simple command with arguments and return value
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! From Rust.", name)
}

// Async command for I/O operations
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    std::fs::read_to_string(&path)
        .map_err(|e| e.to_string())
}

// Command with application state
struct AppState {
    count: Mutex<i32>,
}

#[tauri::command]
fn increment(state: State<AppState>) -> i32 {
    let mut count = state.count.lock().unwrap();
    *count += 1;
    *count
}

// Register commands in the app builder
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(AppState { count: Mutex::new(0) })
        .invoke_handler(tauri::generate_handler![
            greet, read_file, increment
        ])
        .run(tauri::generate_context!())
        .expect("error while running application");
}
// Frontend: calling Rust commands from JavaScript
import { invoke } from "@tauri-apps/api/core";

// Call the greet command
const message = await invoke("greet", { name: "World" });
console.log(message); // "Hello, World! From Rust."

// Call async command with error handling
try {
  const content = await invoke("read_file", {
    path: "/path/to/file.txt"
  });
  console.log(content);
} catch (error) {
  console.error("Failed to read file:", error);
}

// Call stateful command
const newCount = await invoke("increment");
console.log("Count:", newCount);

Events System: Emit and Listen

The events system provides bidirectional communication between the Rust backend and the web frontend. Events are useful for pushing data from Rust to the frontend (like progress updates or real-time notifications) and for sending messages between windows.

Events can be emitted globally (to all windows) or targeted to a specific window. The frontend can listen for events from Rust and vice versa, enabling reactive patterns without polling.

// Rust: emitting events to the frontend
use tauri::{AppHandle, Emitter};

#[tauri::command]
async fn process_files(
    app: AppHandle,
    paths: Vec<String>
) -> Result<(), String> {
    let total = paths.len();
    for (i, path) in paths.iter().enumerate() {
        // Process each file...
        std::thread::sleep(
            std::time::Duration::from_millis(100)
        );

        // Emit progress event to frontend
        app.emit("progress", serde_json::json!({
            "current": i + 1,
            "total": total,
            "file": path
        })).map_err(|e| e.to_string())?;
    }
    Ok(())
}
// Frontend: listening for events from Rust
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";

// Listen for progress events
const unlisten = await listen("progress", (event) => {
  const { current, total, file } = event.payload;
  console.log("Processing " + current + "/" + total
    + ": " + file);
  updateProgressBar(current / total * 100);
});

// Start the process
await invoke("process_files", {
  paths: ["/file1.txt", "/file2.txt"]
});

// Clean up listener when done
unlisten();

Window Management

Tauri provides comprehensive APIs for creating and managing application windows. You can define windows in the configuration file or create them dynamically at runtime from either Rust or JavaScript. Tauri 2.0 also introduces a multiwebview API that allows multiple web views within a single window.

// Frontend: window management
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { getCurrentWindow } from "@tauri-apps/api/window";

// Create a new window
const webview = new WebviewWindow("settings", {
  url: "/settings",
  title: "Settings",
  width: 600,
  height: 400,
  resizable: true,
  center: true,
});

// Control the current window
const appWindow = getCurrentWindow();
await appWindow.setTitle("My Tauri App");
await appWindow.setSize({ width: 800, height: 600 });
await appWindow.center();
await appWindow.setAlwaysOnTop(true);

// Minimize, maximize, close
await appWindow.minimize();
await appWindow.toggleMaximize();
await appWindow.close();

File System Access

Tauri provides file system access through the fs plugin with a scoped security model. Instead of giving the frontend unrestricted access to the entire file system, you define which directories and files the frontend is allowed to read or write through permissions.

// Frontend: file system operations
import {
  readTextFile, writeTextFile,
  readDir, mkdir, remove
} from "@tauri-apps/plugin-fs";
import { appDataDir, join } from "@tauri-apps/api/path";

// Read a file from the app data directory
const dataDir = await appDataDir();
const configPath = await join(dataDir, "config.json");
const content = await readTextFile(configPath);
const config = JSON.parse(content);

// Write a file
await writeTextFile(
  await join(dataDir, "output.txt"),
  "Hello from Tauri!"
);

// List directory contents
const entries = await readDir(dataDir);
for (const entry of entries) {
  console.log(entry.name, entry.isDirectory);
}

// Create a directory
await mkdir(await join(dataDir, "exports"), {
  recursive: true
});

Plugins: Store, SQL, HTTP, Shell

Tauri 2.0 uses a plugin architecture where system capabilities are provided through official and community plugins. The most commonly used plugins include persistent storage, SQLite databases, HTTP clients, and shell command execution.

Store Plugin (Persistent Key-Value Storage)

The store plugin provides a simple persistent key-value store backed by a JSON file. It is ideal for application settings, user preferences, and small data that needs to persist across sessions.

# Install the store plugin
npm run tauri add store

// Frontend: using the store plugin
import { load } from "@tauri-apps/plugin-store";

const store = await load("settings.json");

// Set values
await store.set("theme", "dark");
await store.set("fontSize", 14);
await store.set("user", {
  name: "Alice",
  email: "alice@example.com"
});

// Get values
const theme = await store.get("theme");
const user = await store.get("user");

// Persist to disk
await store.save();

// Listen for changes
await store.onChange((key, value) => {
  console.log("Changed: " + key, value);
});

SQL Plugin (SQLite Database)

The SQL plugin provides a SQLite database for structured data storage. It supports migrations, parameterized queries, and multiple database connections.

# Install the SQL plugin
npm run tauri add sql

// Frontend: using the SQL plugin
import Database from "@tauri-apps/plugin-sql";

// Connect to SQLite database
const db = await Database.load(
  "sqlite:app.db"
);

// Create table
await db.execute(
  "CREATE TABLE IF NOT EXISTS notes ("
  + "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  + "title TEXT NOT NULL, "
  + "content TEXT, "
  + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
);

// Insert data with parameters
await db.execute(
  "INSERT INTO notes (title, content) VALUES (?, ?)",
  ["My Note", "Hello from Tauri SQL"]
);

// Query data
const notes = await db.select(
  "SELECT * FROM notes WHERE title LIKE ?",
  ["%My%"]
);

HTTP Plugin

The HTTP plugin allows the frontend to make HTTP requests through the Rust backend, bypassing CORS restrictions and providing access to system certificates.

# Install the HTTP plugin
npm run tauri add http

// Frontend: making HTTP requests
import { fetch } from "@tauri-apps/plugin-http";

// GET request (bypasses CORS)
const response = await fetch(
  "https://api.example.com/data",
  { method: "GET" }
);
const data = await response.json();

// POST request with JSON body
const result = await fetch(
  "https://api.example.com/submit",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ key: "value" })
  }
);

Shell Plugin

The shell plugin lets you spawn child processes and execute system commands from the frontend, with scoped permissions to control which commands are allowed.

# Install the shell plugin
npm run tauri add shell

// Frontend: executing shell commands
import { Command } from "@tauri-apps/plugin-shell";

// Run a command and get output
const output = await Command.create(
  "git", ["status", "--short"]
).execute();
console.log("stdout:", output.stdout);
console.log("stderr:", output.stderr);

// Stream output from long-running command
const cmd = Command.create("ping", ["localhost"]);
cmd.on("close", (data) => {
  console.log("Exited with code", data.code);
});
cmd.stdout.on("data", (line) => {
  console.log("Output:", line);
});
const child = await cmd.spawn();

Security Model and Permissions

Security is a core design principle of Tauri. The framework implements a defense-in-depth approach with multiple layers: process isolation between the Rust backend and the webview frontend, a granular permissions system that controls API access, Content Security Policy (CSP) enforcement, and scope-based file system restrictions.

In Tauri 2.0, the allowlist from v1 has been replaced by a more powerful permissions system. Each plugin defines its own permissions, and you explicitly grant the frontend access to specific capabilities in your configuration file.

// src-tauri/capabilities/main.json
// Define what the frontend window can access
{
  "identifier": "main-capability",
  "description": "Main window permissions",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:default",
    "fs:allow-read-text-file",
    "fs:allow-write-text-file",
    {
      "identifier": "fs:scope",
      "allow": [
        { "path": "\$APPDATA/**" },
        { "path": "\$DOWNLOAD/**" }
      ]
    },
    "store:default",
    "sql:default",
    "shell:allow-execute",
    {
      "identifier": "shell:allow-spawn",
      "allow": [
        { "cmd": "git", "args": true },
        { "cmd": "node", "args": ["--version"] }
      ]
    },
    "http:default",
    {
      "identifier": "http:scope",
      "allow": [
        { "url": "https://api.example.com/**" }
      ]
    }
  ]
}

Auto-Updater

Tauri includes a built-in auto-update mechanism through the updater plugin. It supports differential updates, signature verification, and custom update endpoints. The updater checks a remote server for new versions and downloads updates in the background.

// tauri.conf.json - updater configuration
{
  "plugins": {
    "updater": {
      "pubkey": "YOUR_PUBLIC_KEY_HERE",
      "endpoints": [
        "https://releases.example.com/"
          + "{{target}}/{{arch}}/{{current_version}}"
      ],
      "windows": {
        "installMode": "passive"
      }
    }
  }
}

// Frontend: check for updates
import { check } from "@tauri-apps/plugin-updater";

const update = await check();
if (update) {
  console.log("Update available: " + update.version);
  // Download and install
  await update.downloadAndInstall();
  // Restart the app to apply
  await import("@tauri-apps/plugin-process")
    .then(p => p.relaunch());
}

Building and Distribution

Tauri produces native application bundles for each target platform. On Windows it generates MSI and NSIS installers, on macOS it creates DMG and app bundles, and on Linux it produces AppImage and deb packages. Cross-compilation is supported through GitHub Actions.

# Build for the current platform
npm run tauri build

# Output locations:
# Windows: src-tauri/target/release/bundle/
#   msi/MyApp_1.0.0_x64.msi
#   nsis/MyApp_1.0.0_x64-setup.exe
#
# macOS: src-tauri/target/release/bundle/
#   dmg/MyApp_1.0.0_aarch64.dmg
#   macos/MyApp.app
#
# Linux: src-tauri/target/release/bundle/
#   appimage/MyApp_1.0.0_amd64.AppImage
#   deb/MyApp_1.0.0_amd64.deb

# Build with debug info
npm run tauri build -- --debug

# Build for a specific target
npm run tauri build -- --target aarch64-apple-darwin

Mobile Support: iOS and Android

Tauri 2.0 brings first-class mobile support, allowing you to build iOS and Android apps from the same codebase used for desktop. The mobile runtime uses WKWebView on iOS and Android WebView, with platform-specific code written in Swift and Kotlin respectively.

Mobile apps access native device features like the camera, GPS, biometrics, and push notifications through Tauri plugins. The development workflow includes hot-reload support for both platforms using the standard iOS Simulator and Android Emulator.

# Initialize mobile targets
npm run tauri android init
npm run tauri ios init

# Development with hot-reload
npm run tauri android dev
npm run tauri ios dev

# Build for mobile
npm run tauri android build
npm run tauri ios build

# Platform-specific plugin (Swift for iOS)
// swift/Sources/ExamplePlugin.swift
// import Tauri
// class ExamplePlugin: Plugin {
//     @objc func getBatteryLevel(
//         _ invoke: Invoke
//     ) {
//         let level = UIDevice.current
//             .batteryLevel
//         invoke.resolve(["level": level])
//     }
// }

Frontend Framework Integration

Tauri is frontend-agnostic and works with any framework that produces HTML, CSS, and JavaScript. The most popular choices are React, Vue, Svelte, and SolidJS. Each can be scaffolded directly through create-tauri-app.

// React + Tauri example
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";

function App() {
  const [result, setResult] = useState("");
  const [name, setName] = useState("");

  async function handleGreet() {
    const msg = await invoke("greet", { name });
    setResult(msg);
  }

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter a name"
      />
      <button onClick={handleGreet}>Greet</button>
      <p>{result}</p>
    </div>
  );
}
<!-- Vue + Tauri example -->
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";

const name = ref("");
const result = ref("");

async function greet() {
  result.value = await invoke("greet", {
    name: name.value
  });
}
</script>

<template>
  <input v-model="name" placeholder="Enter a name" />
  <button @click="greet">Greet</button>
  <p>{{ result }}</p>
</template>
<!-- Svelte + Tauri example -->
<script>
  import { invoke } from "@tauri-apps/api/core";

  let name = "";
  let result = "";

  async function greet() {
    result = await invoke("greet", { name });
  }
</script>

<input bind:value={name} placeholder="Enter a name" />
<button on:click={greet}>Greet</button>
<p>{result}</p>

Performance and Bundle Size

Tauri delivers superior performance compared to Electron across all key metrics. Here are best practices for maximizing performance:

  • Use the OS native webview instead of bundling a browser engine to keep binary sizes under 5 MB.
  • Offload CPU-intensive work to Rust commands instead of running them in JavaScript for significant speed improvements.
  • Use the event system for real-time data streaming instead of polling with repeated invoke calls.
  • Scope file system permissions to only the directories your app needs to reduce the attack surface.
  • Enable the updater plugin with differential updates to minimize download sizes for users.
  • Use the store plugin for lightweight key-value data and the SQL plugin for structured data instead of file-based storage.
  • Leverage Tauri resource system for bundling static assets that the Rust backend needs to access.
  • Configure CSP headers to prevent XSS attacks and restrict which resources the webview can load.
// Example: offload heavy work to Rust for performance
// Instead of processing in JavaScript:
// const result = heavyComputation(data); // Slow in JS

// Define a Rust command for the heavy work
// src-tauri/src/lib.rs
#[tauri::command]
fn process_data(input: Vec<f64>) -> Vec<f64> {
    input.iter()
        .map(|x| x.sqrt() * 2.0 + x.ln())
        .collect()
}

// Call from frontend - runs at native speed
// const result = await invoke("process_data", {
//   input: largeDataSet
// });

Frequently Asked Questions

What is Tauri used for?

Tauri is used for building cross-platform desktop applications with web technologies. Common use cases include developer tools, productivity apps, dashboards, media players, file managers, and system utilities. With Tauri 2.0, it also supports building iOS and Android mobile applications from the same codebase.

Is Tauri better than Electron?

Tauri produces significantly smaller binaries (3-5 MB vs 150+ MB), uses less memory (30-80 MB vs 150-300 MB), and has a stronger security model. However, Electron has a larger ecosystem, more mature tooling, and guaranteed cross-platform rendering consistency since it bundles Chromium. Choose Tauri for performance and security, Electron for maximum browser API compatibility.

Do I need to know Rust to use Tauri?

Basic Tauri apps can be built with minimal Rust knowledge since the scaffolding tool generates the boilerplate. However, as your app grows, you will need Rust for custom commands, system integrations, and performance-critical backend logic. The Tauri command system uses straightforward Rust patterns that are accessible to developers learning the language.

Which frontend frameworks work with Tauri?

Tauri works with any frontend framework that outputs HTML, CSS, and JavaScript. Officially supported scaffolding templates exist for React, Vue, Svelte, SolidJS, Angular, Preact, vanilla JavaScript, and vanilla TypeScript. You can also use meta-frameworks like Next.js, Nuxt, or SvelteKit in static export mode.

Does Tauri support mobile apps?

Yes. Tauri 2.0 introduced first-class support for iOS and Android. You can build mobile apps from the same codebase used for desktop, using WKWebView on iOS and Android WebView. Platform-specific code is written in Swift and Kotlin, and native device features are accessed through Tauri plugins.

How does Tauri handle auto-updates?

Tauri provides a built-in updater plugin that checks a remote endpoint for new versions. It supports signature verification to prevent tampered updates, differential updates to minimize download size, and custom update endpoints. You configure the update URL and public key in tauri.conf.json.

Is Tauri production-ready?

Yes. Tauri 2.0 is stable and used in production by many companies. Notable apps built with Tauri include CrabNebula Cloud, Clash Verge (network proxy), Pake (web-to-desktop wrapper), and various enterprise internal tools. The framework is backed by the Tauri Foundation under the Commons Conservancy.

How do I access the file system in Tauri?

Tauri provides file system access through the fs plugin with scoped permissions. You define which directories the app can access in the capabilities configuration, such as the app data directory, desktop, downloads, or custom paths. The frontend then uses the plugin API to read, write, and manage files within those scopes.

𝕏 Twitterin LinkedIn
Var detta hjälpsamt?

Håll dig uppdaterad

Få veckovisa dev-tips och nya verktyg.

Ingen spam. Avsluta när som helst.

Try These Related Tools

{ }JSON FormatterTSJSON to TypeScriptJSJavaScript Minifier

Related Articles

Electron Complete Guide: Cross-Platform Desktop Apps with Web Technologies (2026)

Comprehensive Electron guide covering main/renderer architecture, IPC communication, preload scripts, BrowserWindow, auto-updater, packaging, native APIs, security, and performance optimization.

Rust Programming Guide: Ownership, Borrowing, Traits, Async Tokio, and Web with Axum

Master Rust programming from scratch. Covers ownership, borrowing, lifetimes, structs/enums/pattern matching, error handling with Result/Option, traits, generics, async with Tokio, common data structures, web APIs with Axum/Actix-web, and Rust vs C++ vs Go comparison.

Web-prestandaoptimering: Core Web Vitals Guide 2026

Komplett guide till webbprestandaoptimering och Core Web Vitals. Forbattra LCP, INP och CLS med praktiska tekniker for bilder, JavaScript, CSS och caching.