Upgrade to Tauri 3.0 Alpha
This guide walks you through upgrading your Tauri 2.0 app to the Tauri 3.0 alpha.
The headline change in Tauri 3.0 is that the webview runtime is no longer selected by Cargo features of the tauri crate.
Your app now depends on a runtime crate directly and selects it when building the app:
tauri-runtime-wryuses the system webview (webkit2gtk on Linux, WebView2 on Windows, and WKWebView on macOS, iOS, and Android). This is what every Tauri 2.0 app uses.tauri-runtime-cefuses the Chromium Embedded Framework, new in 3.0, which ships Chromium with your app.
Read the Webview Runtime guide for the whole picture. The rest of this page lists what changes for an existing app.
Update the Dependencies
Section titled “Update the Dependencies”Tauri 3.0 requires Rust 1.95 or newer. The Rust crates are published as 3.0.0-alpha.x and the npm packages under the next tag:
npm install @tauri-apps/cli@next @tauri-apps/api@nextyarn add @tauri-apps/cli@next @tauri-apps/api@nextpnpm add @tauri-apps/cli@next @tauri-apps/api@nextdeno add npm:@tauri-apps/cli@next npm:@tauri-apps/api@nextbun add @tauri-apps/cli@next @tauri-apps/api@nextcargo install tauri-cli --version "^3.0.0-alpha" --lockedThen update the Rust dependencies and add the runtime crate:
[build-dependencies]tauri-build = { version = "2", features = [] }tauri-build = { version = "3.0.0-alpha.0", features = [] }[dependencies]tauri = { version = "2", features = [] }tauri = { version = "3.0.0-alpha.0", features = [] }tauri-runtime-wry = "3.0.0-alpha.0"Update the Plugins
Section titled “Update the Plugins”The official plugins are published as 3.0.0-alpha.x as well, and their npm packages under the next tag. A 2.x plugin depends on tauri 2 and cannot be registered on a 3.0 app, so bump every tauri-plugin-* crate and @tauri-apps/plugin-* package your app depends on. For example, for the dialog plugin:
[dependencies]tauri-plugin-dialog = "2"tauri-plugin-dialog = "3.0.0-alpha.0"npm install @tauri-apps/plugin-dialog@nextyarn add @tauri-apps/plugin-dialog@nextpnpm add @tauri-apps/plugin-dialog@nextdeno add npm:@tauri-apps/plugin-dialog@nextbun add @tauri-apps/plugin-dialog@nextThe plugin APIs are unchanged from 2.x. The 3.0 alpha of each plugin only updates its tauri dependency and adopts the new autogenerated permission layout, so no code changes are needed on your side. Community plugins must be updated by their authors before they build with Tauri 3.0.
Summary of Changes
Section titled “Summary of Changes”Webview Runtime Selection
Section titled “Webview Runtime Selection”- The
wryandcefCargo features of thetauricrate were removed, along with thex11,dbus, andmacos-proxyfeatures it forwarded to wry. Depend ontauri-runtime-wry(ortauri-runtime-cef) instead, and enable such features on that crate. - The runtime is selected with the new
tauri::Builder::runtimemethod, which takes the attributes type of a runtime crate (tauri_runtime_wry::Wryortauri_runtime_cef::Cef). Building the app without selecting a runtime fails withtauri_runtime::Error::RuntimeNotConfigured. Migration tauri::Builder::default()now uses the type-erasedtauri::DynRuntime, which is also the default runtime type ofAppHandle,App,Window,Webview,WebviewWindow, and the other generic types. Code that namestauri::Wryas the runtime type must be updated. Migration- The
runtime_init_attrsbuilder method was merged intoruntime. - The
tauri::Wryandtauri::WryHandlere-exports were removed. The types live intauri-runtime-wry, where theWry<T>runtime type was renamed toWryRuntime<T>, andWryis now the attributes type that selects it. - The
tauri::taoandtauri::wryre-exports were removed. Usetauri_runtime_wry::{tao, wry}. Migration - Runtime-specific APIs moved from the
tauricrate to extension traits in the runtime crates. Migration tauri::webview_version()was removed, since the version depends on the runtime in use. UseApp::webview_versionorAppHandle::webview_version. Migration- The
tauri_macros::default_runtimeattribute macro was removed. The generic types oftauridefault totauri::DynRuntimedirectly.
Cargo Feature Changes
Section titled “Cargo Feature Changes”devtools,macos-private-api, andunstablemust now be enabled on the runtime crate (tauri-runtime-wryortauri-runtime-cef), which also enables them ontauri. Enabling them ontaurialone no longer enables them on the runtime. Migrationtaurigained thegtk3andgtk4features, which select the Linux GTK bindings. The runtime crates enable the right one for you:tauri-runtime-wryenablesgtk3andtauri-runtime-cefenablesgtk4. Migrationtaurigained thelinux-libappindicatorfeature to keep using libappindicator for the tray icon on Linux. Migration- The
testfeature no longer implies GTK 3, so it can be combined withgtk4.
Linux Changes
Section titled “Linux Changes”- The tray icon uses the ksni backend (StatusNotifierItem over D-Bus) by default instead of libappindicator, dropping the
libayatana-appindicatorsystem dependency. Migration Window::gtk_window,Window::default_vbox,WindowBuilder::transient_for_raw, and the menu integration are gated behind thegtk3andgtk4features. They fail with the newtauri::Error::GtkVersionMismatchunder a runtime whose GTK version is not the one selected at compile time. Migration
Custom Scheme URL
Section titled “Custom Scheme URL”The custom scheme URL format (tauri://localhost or http://tauri.localhost) is now defined by the runtime instead of the platform, and the convertFileSrc JavaScript API takes the format from the runtime.
The wry runtime keeps using http://tauri.localhost on Windows and Android and tauri://localhost elsewhere, so nothing changes for the frontend of an existing app.
tauri::test::MockRuntime uses tauri://localhost on every platform. Tests that sent IPC requests from http://tauri.localhost on Windows and Android must use tauri://localhost instead. Migration
Resources in Development
Section titled “Resources in Development”tauri-build no longer copies the configured bundle > resources to the Cargo target directory. On desktop, unbundled apps (tauri dev and cargo run) resolve resources at runtime from their source paths instead, so editing a resource file no longer triggers a full rebuild of the app:
- When all configured resources are plain relative paths (for example
"assets/*"), the resource directory resolves to the directory containingtauri.conf.json, and files are read directly from their sources. The running app picks up changes live. - When resources are remapped (map notation,
../, or absolute paths), the bundle layout is mirrored next to the executable the first time the resource directory is accessed in each run.
The bundle > resources configuration is now part of the config embedded by generate_context!, where it was previously stripped.
Permissions
Section titled “Permissions”- The autogenerated
allow-$commandanddeny-$commandpermissions are no longer materialized as two permission files per command. The plugin and app manifests store acommandslist instead, and the permissions are resolved on demand, which reduces the size of the ACL embedded in the app. Nothing changes in your capability files. - The app manifest gained implicit
allow-*anddeny-*permissions that allow or deny all of the app’s own commands (the ones listed withtauri_build::AppManifest::commands) through a single resolved entry, so a capability no longer needs to list every command individually. - Plugin and app build scripts write the autogenerated command permissions to
OUT_DIRinstead of the crate’spermissions/autogeneratedfolder. Only the human-readablepermissions/autogenerated/reference.mddocumentation is still written to the plugin crate. Migration
Android Changes
Section titled “Android Changes”The tauri::android_binding! macro moved to tauri_runtime_wry::android_binding!, and #[tauri::mobile_entry_point] expands to it, so Android apps must depend on tauri-runtime-wry. The dependency update above already covers this.
tauri::handle_android_plugin_response and tauri::send_channel_data are exposed on Android so that other runtimes can implement their own binding.
Build Script and Bundler Changes
Section titled “Build Script and Bundler Changes”tauri-builddetects the CEF runtime through the app’stauri-runtime-cefdependency (theDEP_TAURI_RUNTIME_CEF_RUNTIMEenvironment variable it exports to the app’s build script) instead of the removedceffeature oftauri.- The Tauri CLI detects the webview runtime from the
tauri-runtime-wryandtauri-runtime-cefdependencies in the app manifest. The webkit2gtk dependencies of the Debian and RPM packages and the WebView2 installation step of the Windows installers are only added when the app uses wry. The CEF files, code signing entitlements, and macOS dev flow are only used when the app uses CEF. tauri_bundler::BundleSettings::cef_pathandBundleSettings::cef_shared_runtimewere replaced byBundleSettings::webview_runtime, aWebviewRuntimeenum withWry,Cef { distribution, helper }, andOthervariants. This only affects programmatic users of the bundler.tauri-buildsupports Cargo’sbuild-dirlayout (the default since Rust 1.100) when staging external binaries and frameworks.- Added
tauri_build::try_build_contextandContextAttributesfor packages that expandtauri::generate_context!once and share the context with the rest of the workspace. It runs only what the context expansion consumes, and skips app artifact staging and executable-specific build configuration, which stay with the package that owns the binary.
Custom Runtime Implementations
Section titled “Custom Runtime Implementations”These changes to the tauri-runtime crate only matter if you implement your own Runtime:
RuntimeSpecificInitAttrswas renamed toRuntimeInitAttrs. The trait is now generic over the user event type and has atype Runtime: Runtime<T, RuntimeInitAttrs = Self>associated type, so the attributes alone identify the runtime. The implementation for()was removed, so every runtime must define its own attributes type and implementFrom<Self>fortauri_runtime::dynamic::DynRuntimeInitAttrs, so that its attributes can be passed to the type-erased builder.Runtime::WindowOpenerandwindow::WindowBuilderBasenow require'static, so they can be type-erased.Runtime::custom_scheme_urlmoved toRuntimeHandle::custom_scheme_url(&self, scheme, https).- Added the required
RuntimeHandle::webview_versionmethod. - Added the required
WebviewDispatch::with_ios_webviewmethod on iOS. It gives access to the platform webview, plugin manager, and view controller pointers through the newwebview::IosWebviewHandle. WebviewDispatch::open_devtools,close_devtools, andis_devtools_openare required regardless of thedevtoolsfeature, so that the type-erased runtime can forward them. Keep the implementation behind the feature and make it a no-op without it.- The GTK types crossing the runtime boundary are version-agnostic raw pointers, so a runtime can use a GTK version different from the one
tauriwas built with.WindowDispatch::gtk_windowanddefault_vboxreturn*mut c_void(transfer full),WindowBuilder::transient_fortakes a*mut c_void(transfer full), andRawWindow::gtk_windowanddefault_vboxare*mut c_void(transfer none).tauri-runtimeno longer depends on thegtkcrate, and the newtauri_runtime::gtkmodule carries the GTK version a runtime binds to (gtk::declare_version).
New Features
Section titled “New Features”- The Chromium Embedded Framework runtime ships a consistent Chromium version with your app on Windows, macOS, and Linux.
- The type-erased
tauri::DynRuntimelets apps, plugins, and libraries handleAppHandle,Window, andWebviewvalues without naming the runtime type. See Webview Runtime. tauri::webview::PlatformWebview::downcast_refreaches the runtime’s webview type fromWebview::with_webview, whatever the runtime generic in use.- The macOS bundler supports Liquid Glass icons. Add an Icon Composer
.icondirectory (compiled withactoolfrom Xcode 26 or newer) or a prebuiltAssets.carfile tobundle > icon. - The CLI respects the
CARGO_BUILD_TARGETenvironment variable when resolving the build target, matching Cargo’s precedence overbuild.targetin.cargo/config.toml. - When a captured command fails, the CLI and bundler include its stderr in the error message.
Detailed Migration Steps
Section titled “Detailed Migration Steps”Select the Webview Runtime
Section titled “Select the Webview Runtime”Add the runtime crate to your dependencies and pass its attributes to tauri::Builder::runtime:
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .runtime(tauri_runtime_wry::Wry::default()) .run(tauri::generate_context!()) .expect("error while running tauri application");}tauri_runtime_wry::Wry is a unit-like struct. wry has no runtime-wide options, so Wry::default() is all it takes. The CEF runtime’s tauri_runtime_cef::Cef is configured through builder methods instead. See the CEF guide.
Building the app without a runtime fails with tauri_runtime::Error::RuntimeNotConfigured.
Move Cargo Features to the Runtime Crate
Section titled “Move Cargo Features to the Runtime Crate”Enable devtools, macos-private-api, and unstable on the runtime crate. They enable the same feature on tauri, so you do not need to list them twice:
[dependencies]tauri = { version = "2", features = ["devtools", "macos-private-api"] }tauri = "3.0.0-alpha.0"tauri-runtime-wry = { version = "3.0.0-alpha.0", features = ["devtools", "macos-private-api"] }The same applies to the wry-specific x11, dbus, macos-proxy, and tracing features, which are now features of tauri-runtime-wry only. x11 and dbus are enabled by default.
Static Dispatch
Section titled “Static Dispatch”tauri::Builder::default() uses the type-erased tauri::DynRuntime, and so do the default generic parameters of AppHandle<R>, Window<R>, Webview<R>, and the other types.
Code that spelled out tauri::Wry must either drop the type, since the default now works with any runtime, or name the concrete runtime type, tauri_runtime_wry::WryRuntime:
fn setup(app: &tauri::AppHandle<tauri::Wry>) {}fn setup(app: &tauri::AppHandle) {}If you prefer static dispatch, with no trait objects between tauri and the runtime, name the runtime type on the builder. Its runtime method then only accepts the attributes of that runtime:
tauri::Builder::<tauri_runtime_wry::WryRuntime>::new() .runtime(tauri_runtime_wry::Wry::default()) .run(tauri::generate_context!()) .expect("error while running tauri application");In this case the generic types must name the runtime everywhere (tauri::AppHandle<tauri_runtime_wry::WryRuntime>), as they did in 2.0 for any runtime other than tauri::Wry.
Note that tauri_runtime_wry::Wry<T> was renamed to WryRuntime<T>. Wry is now the attributes type that selects the runtime.
Generic code that takes R: tauri::Runtime keeps working with both models, which is what plugins and libraries should keep doing.
Runtime-Specific APIs
Section titled “Runtime-Specific APIs”The APIs that only make sense on one runtime moved from the tauri crate to extension traits in the runtime crates, which now depend on tauri.
The traits are implemented both for the concrete runtime type and for tauri::DynRuntime, and they return tauri_runtime::Error::RuntimeTypeMismatch when the app runs on a different runtime:
| Tauri 2.0 | Tauri 3.0 |
|---|---|
tauri::tao, tauri::wry |
tauri_runtime_wry::tao, tauri_runtime_wry::wry |
AppHandle::create_tao_window, AppHandle::send_tao_window_event |
tauri_runtime_wry::AppHandleWryExt |
App::wry_plugin |
tauri_runtime_wry::AppWryExt (also on AppHandle through AppHandleWryExt) |
Webview::with_webview with the wry webview type |
tauri_runtime_wry::WebviewWryExt::with_wry_webview or PlatformWebview::downcast_ref |
WebviewWindowBuilder::with_environment, with_related_view, with_webview_configuration |
tauri_runtime_wry::WebviewWindowBuilderWryExt (and WebviewBuilderWryExt with the unstable feature) |
The CEF runtime follows the same pattern with tauri_runtime_cef::{WebviewCefExt, WebviewWindowBuilderCefExt, WebviewBuilderCefExt}. See the CEF guide.
Import the trait and the call sites stay the same:
use tauri_runtime_wry::AppHandleWryExt;
fn create_window(app: &tauri::AppHandle) -> tauri::Result<()> { app.create_tao_window(|| ("my window".into(), tauri_runtime_wry::tao::window::WindowBuilder::new()))?; Ok(())}The wry WebviewAttribute enum was replaced by the WryWebviewAttributes struct, with the environment (Windows), related_view (Linux), and webview_configuration (macOS) fields.
The NewWindowResponse::Create documentation now points to the WebviewWindowBuilderWryExt methods to link the new webview to its opener.
Platform Webview Access
Section titled “Platform Webview Access”Webview::with_webview hands out a PlatformWebview that dereferences to the webview type of the runtime generic in use. With the default tauri::DynRuntime, use PlatformWebview::downcast_ref to reach the runtime’s webview type:
use tauri::Manager;
fn zoom(app: &tauri::AppHandle) -> tauri::Result<()> { app.get_webview_window("main").unwrap().with_webview(|webview| { let Some(webview) = webview.downcast_ref::<tauri_runtime_wry::Webview>() else { return; };
#[cfg(target_os = "linux")] { use webkit2gtk::WebViewExt; webview.inner().set_zoom_level(4.); } })}tauri_runtime_wry::WebviewWryExt::with_wry_webview does the downcast for you and fails with RuntimeTypeMismatch on another runtime. The CEF runtime offers tauri_runtime_cef::WebviewCefExt::with_cef_webview for its tauri_runtime_cef::Webview handle.
Webview Version
Section titled “Webview Version”The webview version is now a method of the app instead of a free function:
let version = tauri::webview_version()?;let version = app.webview_version()?;Linux GTK APIs
Section titled “Linux GTK APIs”The tauri crate no longer links GTK unconditionally on Linux. The gtk3 and gtk4 features select the bindings, and the runtime crates enable the right one: tauri-runtime-wry enables gtk3 and tauri-runtime-cef enables gtk4.
An app does not need to do anything. A plugin or library that depends on tauri alone and uses Window::gtk_window, Window::default_vbox, WindowBuilder::transient_for_raw, or the Linux menu integration must enable one of the features explicitly:
[target.'cfg(target_os = "linux")'.dependencies]tauri = { version = "3.0.0-alpha.0", features = ["gtk3"] }gtk = "0.18"Enabling both features selects GTK 4. Cargo’s feature unification enables both whenever the dependency graph contains runtime crates that disagree on the GTK version, and such a binary can only run one of those runtimes, because GTK 3 and GTK 4 cannot be initialized in the same process.
Under a runtime whose GTK version is not the selected one, the GTK APIs and the Linux menu integration fail with tauri::Error::GtkVersionMismatch instead of reinterpreting the runtime’s window objects.
Linux Tray Icon
Section titled “Linux Tray Icon”The tray icon now uses the ksni backend by default, a pure D-Bus implementation of the StatusNotifierItem specification. libayatana-appindicator is no longer required at build time or at runtime, so you can drop it from your system dependencies, CI images, and Debian and RPM package dependencies.
To keep using libappindicator, enable the linux-libappindicator feature:
[dependencies]tauri = { version = "3.0.0-alpha.0", features = ["tray-icon", "linux-libappindicator"] }Mock Runtime Tests
Section titled “Mock Runtime Tests”tauri::test::MockRuntime uses the tauri://localhost custom scheme URL on every platform. Tests that hardcoded the Windows or Android origin when sending IPC requests must use it too:
tauri::test::get_ipc_response( &webview, tauri::webview::InvokeRequest { cmd: "ping".into(), callback: tauri::ipc::CallbackFn(0), error: tauri::ipc::CallbackFn(1), url: "http://tauri.localhost".parse().unwrap(), url: "tauri://localhost".parse().unwrap(), body: tauri::ipc::InvokeBody::default(), headers: Default::default(), invoke_key: tauri::test::INVOKE_KEY.to_string(), },);Plugin Autogenerated Permissions
Section titled “Plugin Autogenerated Permissions”Plugin build scripts now write the autogenerated allow-$command and deny-$command permissions to OUT_DIR, and remove the permissions/autogenerated/commands folder that older versions wrote into the crate’s source tree.
After the first build with Tauri 3.0, delete that folder from version control. Only permissions/autogenerated/reference.md remains in the crate.
If your build script merges permission files from several directories, the new collect_permission_files and define_permissions_from_files functions in tauri_utils::acl::build collect them into a single permission file list.
© 2026 Tauri Contributors. CC-BY / MIT