tauri-apps/tauri
Patterns and conventions
The Tauri codebase is large but consistent. This page captures the conventions that recur across crates so you can match them when you contribute.
Rust
License headers
Every .rs, .ts, .kt, and .swift file starts with:
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT.github/workflows/check-license-header.yml enforces this.
Lint level
Crates that expose a public API use #![warn(missing_docs, rust_2018_idioms)] (e.g. crates/tauri/src/lib.rs, crates/tauri-bundler/src/lib.rs). Internal crates relax this. Clippy is run with -D warnings in CI, so any new warning is a CI fail.
Formatting
rustfmt.toml at the repo root pins the project style. cargo fmt --all is the only correct invocation; do not hand-format.
Error handling
- Public APIs export their own
Errorenum andpub type Result<T> = std::result::Result<T, Error>. Examples:crates/tauri/src/error.rs,crates/tauri-bundler/src/error.rs,crates/tauri-cli/src/error.rs. - Errors use
thiserror::Errorfor variants and#[non_exhaustive]to allow additive changes without a breaking version bump. - The CLI specifically uses
anyhowfor internal command implementations and converts to its ownErrorat command boundaries (crates/tauri-cli/src/error.rsdefines aContextextension trait).
Async runtime
The core crate exposes its own tauri::async_runtime (crates/tauri/src/async_runtime.rs) wrapping Tokio. Do not import tokio directly in crates/tauri/; use the wrapper so the runtime is configurable.
Public re-exports
crates/tauri/src/lib.rs is the canonical export surface. New public types should be re-exported there with pub use .... Plugin authors are expected to depend only on tauri, never on tauri-runtime or tauri-runtime-wry directly.
Conditional compilation
Three flavours of cfg show up across the workspace:
- Target-OS gates:
#[cfg(target_os = "macos")],#[cfg(windows)],#[cfg(any(target_os = "linux", …))]. - Tauri convenience aliases:
#[cfg(desktop)],#[cfg(mobile)],#[cfg(target_vendor = "apple")]. These are defined incrates/tauri/build.rsviacargo:rustc-cfg. - Cargo features:
#[cfg(feature = "tray-icon")],#[cfg(feature = "tracing")], etc.
When adding a new platform-specific code path, prefer desktop/mobile aliases when they apply.
Builder pattern
Almost every public type uses a builder:
tauri::Builder(crates/tauri/src/app.rs)tauri::WebviewWindowBuilder(crates/tauri/src/webview/webview_window.rs)tauri::WindowBuilder,tauri::WebviewBuilder(crates/tauri/src/window/,crates/tauri/src/webview/)tauri::menu::MenuBuilder,SubmenuBuilder,MenuItemBuilder,CheckMenuItemBuilder, … (crates/tauri/src/menu/builders/)tauri::tray::TrayIconBuildertauri::plugin::Builder(crates/tauri/src/plugin.rs)
Builders take self by value and return self. New configuration goes in as a new chainable method, not a field on a struct passed to new.
Macros
The #[tauri::command] and tauri::generate_handler! macros are the public interface for IPC. They live in crates/tauri-macros/src/command/ and are kept stable. When you add a new argument extractor (e.g. State, Window, AppHandle), update both the macro and the CommandArg impls under crates/tauri/src/ipc/command.rs.
JavaScript / TypeScript
Module shape
packages/api/src/index.ts re-exports every module as a namespace. New public modules must be added there and exported from package.json's exports map (packages/api/package.json).
TypeScript style
- ESM source, dual ESM/CJS output via Rollup (
packages/api/rollup.config.ts). - ESLint config:
packages/api/eslint.config.jswitheslint-plugin-security. - No
anyin public API surface; useunknownand narrow.
Talking to Rust
Everything from JS to Rust goes through core.invoke<T>(name, args) in packages/api/src/core.ts. New high-level APIs should call into invoke with a stable command name namespaced under a plugin (e.g. plugin:window|set_title).
ACL / capabilities / permissions
Plugins ship a permissions/ directory with one TOML file per permission. crates/tauri-plugin/src/build.rs generates JSON Schemas and writes the permission list to OUT_DIR so downstream apps can validate their capabilities at compile time. Adding a permission means:
- Add the TOML under your plugin's
permissions/. - Update
default.tomlif it should be granted by default. - Re-run
cargo run -p tauri-schema-generatorif your change affects the global schemas.
See systems/acl-and-capabilities for the runtime side.
Documentation
- All public Rust items must carry doc comments (
#![warn(missing_docs)]). - Use full paths (
tauri::ipc::Channel, not justChannel) when referring to types from a doc comment in another module — the docs.rs build catches these. - Examples in doc comments are run by
cargo test --docand must compile. Use# fn main() { ... }to hide setup boilerplate.
Testing conventions
- Unit tests inline as
#[cfg(test)] mod tests. - Multi-crate tests in
crates/tests/<scenario>/. - For the core crate, gate webview-bound tests behind the
testfeature so they pull in the mock runtime undercrates/tauri/src/test/.
Commit messages
Conventional commits with a scope are the norm:
feat(macros): support optional argumentsfix(tauri-build): preserve numeric semver build metadatachore(deps): update rust crate muda to 0.19docs: nsis default languages
Browse git log --oneline -100 for accurate examples.
Renovate / Dependabot
Most dependency updates land via Renovate (renovate.json) and Dependabot (dependabot.yml). When reviewing one, the bar is "does CI pass and is the changelog innocuous?" For semver-major bumps the maintainer doing the review should also smoke-test an example app.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.