Open-Source Wikis

/

Tauri

/

How to contribute

/

Patterns and conventions

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 Error enum and pub 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::Error for variants and #[non_exhaustive] to allow additive changes without a breaking version bump.
  • The CLI specifically uses anyhow for internal command implementations and converts to its own Error at command boundaries (crates/tauri-cli/src/error.rs defines a Context extension 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 in crates/tauri/build.rs via cargo: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::TrayIconBuilder
  • tauri::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.js with eslint-plugin-security.
  • No any in public API surface; use unknown and 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:

  1. Add the TOML under your plugin's permissions/.
  2. Update default.toml if it should be granted by default.
  3. Re-run cargo run -p tauri-schema-generator if 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 just Channel) 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 --doc and 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 test feature so they pull in the mock runtime under crates/tauri/src/test/.

Commit messages

Conventional commits with a scope are the norm:

  • feat(macros): support optional arguments
  • fix(tauri-build): preserve numeric semver build metadata
  • chore(deps): update rust crate muda to 0.19
  • docs: 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.

Patterns and conventions – Tauri wiki | Factory