← Catalog

tauri-setup-v1v2

Use when migrating an existing Tauri 1.x app to Tauri 2.0 — running `tauri migrate`, rewriting `tauri.conf.json` (allowlist → capabilities, bundle moves, devPath/distDir renames), updating Rust APIs (Window → WebviewWindow, `tauri::api::*` → plugin crates), updating JS imports (`@tauri-apps/api/tauri` → `/core`, fs/dialog/shell/http moved to plugins), and bumping Cargo deps to `tauri = "2"` / `tauri-build = "2"`.

Tauri 1.x → 2.0 Migration

Tauri 2.0 is not a drop-in upgrade. The allowlist is replaced by capabilities, dozens of APIs moved to plugin crates, and the Rust Window type was renamed to WebviewWindow to make room for multi-webview. Most of the mechanical work is handled by tauri migrate, but you must still: (a) re-grant permissions via capabilities, (b) add the plugin crates you actually use, (c) rename JS imports.

Work through templates/migration-checklist.md — it’s the same list seasoned Tauri maintainers walk top-to-bottom.

Step 0 — Automated migration

# pick the package manager you use:
npm  install @tauri-apps/cli@latest && npm run tauri migrate
pnpm update  @tauri-apps/cli@latest && pnpm tauri migrate
bun  add -d  @tauri-apps/cli@latest && bunx tauri migrate
cargo install tauri-cli --version "^2.0.0" --locked && cargo tauri migrate

tauri migrate rewrites tauri.conf.json, converts the v1 allowlist into a default.json capability file, bumps Cargo deps, and adds the plugin crates that map to allowlist entries. It does not touch your Rust source or JS imports — those are manual.

The CLI prints a TODO list at the end. Read it; treat it as additive to this skill, not redundant.

Step 1 — tauri.conf.json schema diff

Key moves (run after migrate to verify):

- "package": { "productName": "MyApp", "version": "0.1.0" }
+ "productName": "MyApp",
+ "version": "0.1.0",
+ "mainBinaryName": "MyApp",
+ "identifier": "com.example.myapp",

- "tauri": {
-   "allowlist": { "fs": { "all": true }, "shell": { "open": true } },
-   "bundle": { "identifier": "com.example.myapp", "dmg": {...} },
-   "windows": [ { "fileDropEnabled": true } ],
-   "updater": { "active": true, "endpoints": [...] },
-   "systemTray": { "iconPath": "icons/tray.png" }
- },
+ "app": {
+   "windows": [ { "dragDropEnabled": true } ],
+   "withGlobalTauri": true,
+   "trayIcon": { "iconPath": "icons/tray.png" },
+   "security": { "assetProtocol": { "scope": [...] } }
+ },
+ "bundle": {
+   "macOS": { "dmg": {...} },
+   "linux": { "deb": {...}, "appimage": {...} },
+   "createUpdaterArtifacts": true
+ },
+ "plugins": {
+   "updater": { "endpoints": [...] }
+ },

- "build": { "distDir": "../dist", "devPath": "http://localhost:1420" }
+ "build": { "frontendDist": "../dist", "devUrl": "http://localhost:1420" }

Renames at a glance:

  • package.productName/version → top-level
  • tauriapp
  • tauri.allowlistdeleted, replaced by src-tauri/capabilities/*.json
  • tauri.bundle → top-level bundle; dmg/deb/appimage nested under macOS/linux
  • tauri.windows[].fileDropEnabledapp.windows[].dragDropEnabled
  • tauri.updaterplugins.updater; also set bundle.createUpdaterArtifacts: true (use "v1Compatible" if your installed users came from v1)
  • tauri.systemTrayapp.trayIcon
  • build.distDirbuild.frontendDist
  • build.devPathbuild.devUrl
  • build.withGlobalTauriapp.withGlobalTauri

Step 2 — Cargo.toml

 [build-dependencies]
-tauri-build = { version = "1", features = [] }
+tauri-build = { version = "2", features = [] }

 [dependencies]
-tauri = { version = "1", features = ["api-all", "system-tray", "updater"] }
+tauri = { version = "2", features = [] }
+tauri-plugin-shell = "2"        # if you used shell.open / Command
+tauri-plugin-dialog = "2"       # if you used dialog
+tauri-plugin-fs = "2"           # if you used fs
+tauri-plugin-http = "2"         # if you used http
+tauri-plugin-notification = "2" # if you used notification
+tauri-plugin-updater = "2"      # if you used the updater
+tauri-plugin-clipboard-manager = "2"
+tauri-plugin-process = "2"
+tauri-plugin-os = "2"

 [lib]                           # required for mobile targets
 name = "app_lib"
 crate-type = ["staticlib", "cdylib", "rlib"]

Removed feature flags: api-all, system-tray, updater, reqwest-client, process-command-api, shell-open-api. Each became a separate plugin crate.

Step 3 — Mobile-ready Rust entrypoint

// src-tauri/src/lib.rs  (new file; rename of old main.rs)
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_dialog::init())
        // ... register each plugin you depend on
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { app_lib::run(); }

Step 4 — Rust API renames

v1v2
tauri::Windowtauri::WebviewWindow
WindowBuilderWebviewWindowBuilder
WindowUrlWebviewUrl
Manager::get_window(label)Manager::get_webview_window(label)
tauri::api::path::* / PathResolverapp.path() (on Manager)
tauri::api::dialog::*tauri_plugin_dialog::DialogExt
tauri::api::http::*tauri_plugin_http (re-exports reqwest)
tauri::api::shell::* / api::process::Commandtauri_plugin_shell::ShellExt
tauri::api::notification::*tauri_plugin_notification::NotificationExt
tauri::api::os::*tauri_plugin_os::*
tauri::api::process::{current_binary, restart}tauri::process::* (app.handle().restart() / .exit() via process plugin)
App::clipboard_manager()tauri_plugin_clipboard_manager::ClipboardExt
App::global_shortcut_manager()tauri_plugin_global_shortcut::GlobalShortcutExt
SystemTray / SystemTrayMenutauri::tray::TrayIconBuilder + tauri::menu::*
Menu, CustomMenuItem, Submenu, Builder::on_menu_eventtauri::menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder} + app.on_menu_event(..)

Builder::on_menu_event is removed — use app.on_menu_event(..) or WebviewWindow::on_menu_event(..).

Step 5 — JavaScript API renames

- import { invoke } from "@tauri-apps/api/tauri";
+ import { invoke } from "@tauri-apps/api/core";

- import { WebviewWindow } from "@tauri-apps/api/window";
+ import { WebviewWindow } from "@tauri-apps/api/webviewWindow";

These plugins moved out of @tauri-apps/api — install the matching @tauri-apps/plugin-* package:

v1 importv2 package
@tauri-apps/api/fs@tauri-apps/plugin-fs
@tauri-apps/api/dialog@tauri-apps/plugin-dialog
@tauri-apps/api/shell@tauri-apps/plugin-shell
@tauri-apps/api/http@tauri-apps/plugin-http
@tauri-apps/api/notification@tauri-apps/plugin-notification
@tauri-apps/api/clipboard@tauri-apps/plugin-clipboard-manager
@tauri-apps/api/global-shortcut@tauri-apps/plugin-global-shortcut
@tauri-apps/api/os@tauri-apps/plugin-os
@tauri-apps/api/process@tauri-apps/plugin-process
@tauri-apps/api/cli@tauri-apps/plugin-cli
@tauri-apps/api/updater@tauri-apps/plugin-updater

fs plugin also renamed functions: createDirmkdir, readBinaryFilereadFile, removeDir/removeFileremove, renameFilerename, writeBinaryFilewriteFile. Dir enum → BaseDirectory.

Step 6 — Permissions (the big one)

The v1 allowlist is gone. v2 uses capabilities — files in src-tauri/capabilities/ that list which windows can call which plugin commands. tauri migrate generates default.json for you; review it.

Minimum capability for a default app:

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "default capability for main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "shell:allow-open",
    "dialog:default"
  ]
}

If you see runtime errors like command shell|open not allowed by ACL, that’s a missing permission — see the tauri-security skill for the full ACL model.

Step 7 — Env vars

CLI/build env vars were renamed (most for consistency). Common ones:

  • TAURI_PRIVATE_KEYTAURI_SIGNING_PRIVATE_KEY
  • TAURI_KEY_PASSWORDTAURI_SIGNING_PRIVATE_KEY_PASSWORD
  • TAURI_PLATFORMTAURI_ENV_PLATFORM
  • TAURI_DEBUGTAURI_ENV_DEBUG

Update CI scripts and .env files accordingly.

Step 8 — Windows origin URL

In production on Windows, frontend now loads from http://tauri.localhost (not https://). IndexedDB / LocalStorage / Cookies that existed under the v1 https origin will appear empty to v2 users. If you have shipped users, set:

"app": { "windows": [{ "useHttpsScheme": true }] }

so their stored data survives the upgrade.

Step 9 — Updater

Built-in updater dialog is gone. You must call check() / downloadAndInstall() yourself (JS or Rust). Set bundle.createUpdaterArtifacts: true (or "v1Compatible" for existing v1 users). See tauri-updater skill.

Verifying the migration

cargo tauri info     # confirms plugin crates registered, env detected
cargo tauri dev      # boots the app; watch for ACL errors in devtools console
cargo tauri build    # bundles; confirm artifacts under src-tauri/target/release/bundle/

If dev errors with command X not allowed, add the permission to a capability file. If build errors about createUpdaterArtifacts, add it under bundle. If JS imports throw module not found, you missed a @tauri-apps/plugin-* install.