Introduction to the Dialog API

Learn what the Tauri v2 Dialog API is, how to set it up, required permissions, and how to use native file and message dialogs from a React frontend.

Tauri applications run in a native window, but the web content inside it has no direct access to the operating system's file pickers, message boxes, or save prompts. Browsers enforce this gap for security. The Dialog API bridges it by providing a safe, cross‑platform way to open native dialogs from your React frontend or directly from Rust.

What Is the Dialog API?

The Dialog API is a Tauri v2 plugin that lets you display native operating system dialogs: file open/save pickers, folder selectors, and message boxes (info, warning, error, and confirmation). It wraps the platform’s native dialog implementation, so the dialogs look and behave like any other application on the user’s system.

Scope:

The Dialog API handles only the interaction with the native dialog — selecting files, confirming an action, or viewing a message. Actually reading or writing the selected files requires the File System API or the standard Rust std::fs module, which are covered later in this chapter.

The plugin exposes both a JavaScript API (for use in your React components) and a Rust API (for use inside Tauri commands). Both ultimately invoke the same native dialog backend, so you can choose whichever fits your architecture.

Why Use Native Dialogs?

Web‑based file inputs and alert boxes are limited. A native file dialog offers:

  • Access to the real filesystem (network drives, removable media, cloud‑synced folders) rather than a sandboxed virtual filesystem.
  • File type filters that match what the OS supports — e.g., filtering by extension or MIME type with OS‑native presentation.
  • System‑standard keyboard shortcuts, drag‑and‑drop, and navigation that users already know.
  • Message boxes with native styling, including the OS‑standard button order and system sounds.

For a desktop application, these are not cosmetic differences. They remove the “website wrapped in a frame” feeling and make the app behave like a proper native tool.

Installing the Dialog Plugin

The dialog functionality is not part of Tauri’s core; it lives in a separate plugin. You need to add it on both the Rust side and the JavaScript side, then wire it up.

1

Step 1: Add the Rust plugin

Run this command from the root of your Tauri project (the directory containing src-tauri):

cargo add tauri-plugin-dialog

This adds tauri-plugin-dialog to your Cargo.toml dependencies. The official build requires Rust 1.77.2 or later.

2

Step 2: Register the plugin in your Rust backend

Open src-tauri/src/lib.rs and add the plugin initialization inside the tauri::Builder chain. Your file should already have a run function; insert the .plugin(...) call before the .run(...):

src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())  // Register the dialog plugin
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

One line does it:

Once this is in place, the plugin is active and ready to serve dialog requests. No further Rust configuration is required for basic usage.

3

Step 3: Install the JavaScript package

The frontend needs the @tauri-apps/plugin-dialog npm package to call the dialogs from React. Install it with your package manager:

npm install @tauri-apps/plugin-dialog

This package provides functions like open, save, message, ask, and confirm that you can import directly into your React components.

Configuring Permissions

Tauri v2 uses a capability‑based security model. Every plugin operation that touches the system needs an explicit permission, or the call will be blocked silently (or with an error in development).

The dialog plugin ships with a default permission set that enables all dialog types: allow-open, allow-save, allow-message, and their aliases. When you installed the plugin, this permission set was added to your app’s capabilities automatically — typically in the default capability located at src-tauri/capabilities/default.json.

You can verify the permissions are active by checking that file. It should contain an entry like this:

src-tauri/capabilities/default.json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "dialog:default"   // Enables open, save, and message dialogs
  ]
}

Missing dialog:default?:

If your capability file does not include "dialog:default" (or the individual permissions like "dialog:allow-open"), any call to the dialog functions will fail. In development mode you'll see a console error; in production the call simply returns null or false without explanation.

If you prefer granular control, you can replace "dialog:default" with only the permissions you need — for example, "dialog:allow-open" and "dialog:allow-message". The full permission table is available in the official plugin documentation.

A Basic Example: Opening a File

With the plugin installed and permissions granted, you can open a native file picker from a React component. The function open from @tauri-apps/plugin-dialog is asynchronous and returns the file path as a string (or null if the user cancels).

Here is a minimal, fully working component:

src/App.tsx
import { useState } from "react";
import { open } from "@tauri-apps/plugin-dialog";
function App() {
  const [filePath, setFilePath] = useState<string | null>(null);
  async function handleOpenFile() {
    const selected = await open({
      multiple: false,
      directory: false,
    });
    if (selected) {
      setFilePath(selected as string);
    }
  }
  return (
    <div style={{ padding: "2rem" }}>
      <h1>Dialog API Demo</h1>
      <button onClick={handleOpenFile}>Pick a file</button>
      {filePath && <p>Selected: {filePath}</p>}
    </div>
  );
}
export default App;

The open function accepts an options object. Setting directory: false and multiple: false makes it behave as a single‑file picker, which is the most common use case.

When you click the button, the OS‑native file dialog appears. After you select a file and confirm, the component state updates with the absolute path. If you cancel, selected is null and nothing changes.

Everything working?:

Run npm run tauri dev. Click the button, pick any file, and you should see its full path printed below the button. That confirms the dialog plugin is correctly installed, permitted, and wired.

Available Dialog Types

The plugin groups the native dialogs into three categories, each available from both JavaScript and Rust. The table below summarizes them; detailed explanations and code examples follow.

Dialog CategoryJavaScript FunctionsPurpose
File dialogsopen, savePick existing files/folders or choose a save destination
Message dialogsmessage, ask, confirmDisplay informational, warning, or error messages; ask yes/no or ok/cancel questions
(Rust‑only builder)DialogExt::dialog().file() / message()Chainable builder for custom dialogs invoked directly from Rust commands

The JavaScript ask and confirm functions were historically separate from message, but in Tauri v2 they all map to the same message dialog system with different button presets. The Rust side uses a single builder pattern for all message variants.

Blocking vs Non‑Blocking Calls

The dialog plugin offers both blocking and non‑blocking methods, especially on the Rust side. The choice affects what happens to the rest of your app while the dialog is open.

  • Blocking (Rust: blocking_show, blocking_pick_file): The thread that calls the dialog pauses until the user dismisses it. No other code on that thread runs. If you call a blocking dialog from the main thread or from an event handler that runs on the same thread, the entire app — including window redrawing — freezes. This is the root cause of the “crashed the program after few seconds” behavior some developers encounter.
  • Non‑blocking (JavaScript: all functions return a Promise; Rust: show(), pick_file() with a callback): The call returns immediately. In JavaScript, you await the promise, and the rest of your component can still render and respond to other events. In Rust, you pass a closure that runs when the dialog closes.

Never block the main thread:

In a Tauri v2 Rust command that runs on the main thread, calling a blocking dialog method will lock the entire window. Always prefer the async versions from JavaScript, or use the non‑blocking Rust builders with callbacks. If you must use a blocking call in Rust, spawn it on a separate thread or use tauri::async_runtime::spawn_blocking.

From the React frontend, all dialog functions are promise‑based, so blocking is never an issue. The rule is simple: if you see blocking_ in a Rust method name, think twice before calling it outside a dedicated worker thread.

Common Mistakes

This section addresses the most frequent errors developers hit when first working with the Dialog API in Tauri v2.

Permission errors with no visible feedback:

If you forget to include dialog:default in your capability file, the dialog functions will fail silently. The JavaScript promise resolves to null or false with no error message in the console (in production builds). Always verify the permissions first when a dialog call returns nothing unexpectedly.

Using Tauri v1 API syntax:

The v1 tauri::api::dialog module and the @tauri-apps/api dialog methods (e.g., dialog.ask) no longer exist in v2. All dialog functionality moved to the plugin. Using old imports will cause build errors or runtime failures. Always use @tauri-apps/plugin-dialog on the frontend and tauri_plugin_dialog in Rust.

A third subtlety: the file path returned by open is an absolute system path on desktop platforms, but on some platforms (like Android) it may be a content URI. Always handle the returned value as a generic string path, and use the Path API to manipulate it when needed.

Summary

The Dialog API is the gateway to native file interactions in your Tauri application. You installed the plugin, activated the default permissions, and wrote a React component that opens a system file picker — a pattern that scales to every dialog type.

The key takeaways:

  • The plugin must be registered on both the Rust side (lib.rs) and the frontend (@tauri-apps/plugin-dialog).
  • Permissions are controlled through capability files; dialog:default covers all common dialog operations.
  • Prefer promise‑based JavaScript calls from React to avoid blocking the UI thread.
  • The API is intentionally small: open, save, message, ask, and confirm cover the vast majority of desktop dialog needs.