Environment Variables

How environment variables work in Tauri v2 apps during development and production builds, including Vite, Rust, and CI usage

Environment variables in a Tauri application are values the operating system passes to your program. They let you change behavior without touching code — different API endpoints for development and production, signing credentials that should never be committed to a repository, or flags that control how the Tauri CLI builds your app.

A Tauri project has two sides that care about environment variables: the frontend (React + Vite) and the Rust backend. Each handles them differently, and both matter for a working development cycle and a correct production build.

Development Environment

The variables you use while running tauri dev or tauri build --debug fall into three buckets: Vite variables that configure your React code, Rust variables your backend reads at runtime, and CLI variables that influence how Tauri starts and watches your project. The development environment page covers this setup in isolation.

Using .env files with Vite

Vite loads .env files automatically from your project root. By default it supports .env, .env.development, and .env.production, among others. Only variables prefixed with VITE_ are exposed to your frontend code — everything else stays hidden.

Create a .env.development file at the top level of your project (next to package.json):

VITE_API_URL=http://localhost:3001/api
VITE_APP_TITLE=MyApp Dev

In any React component, you access these with import.meta.env:

import { useState, useEffect } from "react";
function App() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(`${import.meta.env.VITE_API_URL}/status`)
      .then((res) => res.json())
      .then(setData);
  }, []);
  return (
    <div>
      <h1>{import.meta.env.VITE_APP_TITLE}</h1>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>Loading...</p>}
    </div>
  );
}
export default App;

Missing VITE_ prefix:

Vite intentionally ignores environment variables without the VITE_ prefix. If you write API_URL=http://localhost and try to read import.meta.env.API_URL, it will be undefined. Always start your client-side variables with VITE_.

These values are replaced at build time — they are not read at runtime. Changing .env while tauri dev is running requires restarting the dev server for Vite to pick up the new values.

Reading environment variables from Rust

The Rust backend can read system environment variables at any point with std::env::var. This is useful for secrets that should never appear in frontend bundles, or for values you need only in native code.

You can expose a specific variable to the frontend through a Tauri command. This lets React request the value without embedding it in the JavaScript bundle.

Add a command in your Rust source:

#[tauri::command]
fn get_env(key: String) -> Result<String, String> {
    std::env::var(&key).map_err(|_| format!("Environment variable '{}' not set", key))
}
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![get_env])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Then call it from the frontend:

import { invoke } from "@tauri-apps/api/core";
import { useState } from "react";
function App() {
  const [rustVar, setRustVar] = useState("");
  const fetchEnv = async () => {
    const value = await invoke("get_env", { key: "SHELL" });
    setRustVar(value as string);
  };
  return (
    <div>
      <button onClick={fetchEnv}>Read SHELL from Rust</button>
      {rustVar && <p>Shell: {rustVar}</p>}
    </div>
  );
}
export default App;

Do not expose all environment variables:

A command that returns any arbitrary environment variable by name is a security risk if left uncontrolled. In production, restrict which keys the frontend can request, or have dedicated commands for each sensitive value rather than a generic get_env.

CLI variables that affect development

Tauri's CLI reads several environment variables that change how tauri dev behaves. These are useful for customizing the dev server, watching, and hook behavior.

  • TAURI_CLI_PORT — The port the CLI's built-in dev server listens on. If your frontend requires a specific port, set this before running tauri dev.
  • TAURI_CLI_WATCHER_IGNORE_FILENAME — Name of a .gitignore-style file that tells the file watcher which paths to ignore during development. The CLI checks each directory for this file.
  • TAURI_CLI_NO_DEV_SERVER_WAIT — Set to 1 or true to skip waiting for the frontend dev server to start. This is helpful when you run Vite separately and Tauri only needs to connect to an already-running server.
  • CI — When set, the CLI runs without interactive prompts. GitHub Actions and other CI environments set this automatically.
  • TAURI_CLI_CONFIG_DEPTH — Number of directory levels to search upward for a Tauri config file.

Hook command variables

When you define beforeDevCommand or beforeBuildCommand in tauri.conf.json, the CLI sets environment variables that the command can use to decide what to do. The most important for development is TAURI_ENV_DEBUG — it is true when running tauri dev or tauri build --debug, and false for release builds.

This lets a single frontend build script adapt its behavior:

"build": {
  "beforeDevCommand": "npm run dev",
  "beforeBuildCommand": "npm run build",
  ...
}

And in your package.json:

"scripts": {
  "dev": "vite",
  "build": "sh -c 'if [ \"$TAURI_ENV_DEBUG\" = \"true\" ]; then npm run build:debug; else npm run build:release; fi'",
  "build:debug": "vite build --mode development",
  "build:release": "vite build --mode production"
}

On Windows, the equivalent would use cmd /c or a cross-platform Node script rather than a shell conditional.

Correct setup confirmation:

If you set a VITE_ variable in .env.development and it appears in your React app while running tauri dev, and TAURI_ENV_DEBUG prints true inside a hook command, your development environment is wired correctly.

Production Environment

A production build involves signing, notarizing, and packaging your app. Environment variables supply the secrets for these steps without embedding them in configuration files. The production environment page lists the variables in isolation. The variables fall into several groups: signing, Apple notarization, bundler tooling, and platform-specific flags.

Signing application bundles

Tauri supports code signing with a private key and password. These are passed as environment variables so they never appear in source control.

  • TAURI_SIGNING_PRIVATE_KEY — The private key, either as a string or a path to a key file.
  • TAURI_SIGNING_PRIVATE_KEY_PASSWORD — The password for that private key.

During a CI build, you store these as secrets in your pipeline and inject them into the step that runs tauri build:

- name: Build Tauri app
  run: |
    npm ci
    npx tauri build
  env:
    TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
    TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}

Apple notarization and iOS signing

For macOS and iOS targets, Apple requires authentication through your developer account. Tauri accepts these environment variables:

  • APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID — Used for notarization with an Apple ID and app-specific password.
  • APPLE_API_KEY / APPLE_API_ISSUER / APPLE_API_KEY_PATH — JWT-based authentication as an alternative to Apple ID credentials.
  • APPLE_CERTIFICATE — Base64-encoded .p12 certificate for code signing.
  • APPLE_CERTIFICATE_PASSWORD — The password for that certificate.
  • APPLE_SIGNING_IDENTITY — The identity used to sign the app. Overrides tauri.conf.json > bundle > macOS > signingIdentity.

Prefer JWT authentication:

Apple's API key approach (JWT) does not require two-factor authentication or app-specific passwords, making it more reliable in automated CI pipelines. When possible, use APPLE_API_KEY and APPLE_API_ISSUER over APPLE_ID and APPLE_PASSWORD.

Linux-specific bundling

  • TAURI_SIGNING_RPM_KEY — A private GPG key exported to ASCII-armored format for signing RPM packages.
  • TAURI_SIGNING_RPM_KEY_PASSPHRASE — Passphrase for that GPG key, if one was set.
  • TAURI_LINUX_AYATANA_APPINDICATOR — Set to true or 1 to force the use of libayatana-appindicator for system tray icons instead of the older libappindicator.

Windows-specific bundling

  • TAURI_WINDOWS_SIGNTOOL_PATH — Path to signtool.exe for code signing Windows binaries.
  • TAURI_BUNDLER_WIX_FIPS_COMPLIANT — If set, the WiX installer will enable FIPS compliance.

Bundler tooling mirrors and sidecar signing

  • TAURI_BUNDLER_TOOLS_GITHUB_MIRROR / TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE — Specify a mirror URL for downloading bundler tools. Useful in environments where GitHub is blocked or slow. The template form, e.g. https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>, lets you replace placeholders at runtime.
  • TAURI_SKIP_SIDECAR_SIGNATURE_CHECK — Skips signature verification for sidecar binaries.

Mobile target paths

  • TAURI_ANDROID_PROJECT_PATH — Path to the Android project directory, typically <project>/src-tauri/gen/android.
  • TAURI_IOS_PROJECT_PATH — Path to the iOS project directory, typically <project>/src-tauri/gen/ios.

Environment variables in the built application

Once packaged, your Tauri app runs as a native binary. Environment variables set on the user's system are available to the Rust backend via std::env::var. Vite's import.meta.env.VITE_* variables, however, were replaced at build time — they are not dynamic in the shipped executable.

If you need runtime configuration that changes between environments without rebuilding, keep those values in the Rust side and read them from the system environment or a configuration file.

CI/CD workflow summary

A typical GitHub Actions build step for a signed Tauri v2 release looks like this:

- name: Build Tauri app
  uses: tauri-apps/tauri-action@v0
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
    TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
  with:
    args: --target ${{ matrix.target }}

If you keep beforeBuildCommand in tauri.conf.json and need frontend environment variables at build time, attach them to the same step that runs the Tauri build — the beforeBuildCommand inherits the step's environment.

Tauri CLI does not load .env files:

The Tauri CLI itself does not parse .env files. If your beforeBuildCommand runs npm run build, and your build script relies on Vite's dotenv loading, then you're fine — Vite handles that. But if you set a variable expecting the Rust side to pick it up from a .env file, it won't happen. Use the actual system environment or a crate like dotenvy in Rust if you need file-based loading.


Environment variables in Tauri bridge two distinct concerns: build-time configuration for the frontend and runtime configuration for the backend. The frontend gets its values baked in; the backend reads them live. Keeping that distinction clear — and never confusing Vite’s static replacement with Rust’s dynamic lookup — prevents most of the mistakes that developers hit when moving from development to production.

Development Environment

Learn how to manage environment variables in a Tauri v2 development setup with React and Vite, covering .env files, frontend and backend variable access, and common pitfalls.

Production Environment

Manage environment variables for Tauri v2 production builds with React and Vite, including secrets handling, platform differences, and configuration best practices.