Dialog API
Learn how to use native system dialogs for file selection, folder browsing, saving files, and displaying messages in Tauri v2 with React
The Dialog API lets your Tauri application open the operating system's own file picker, folder chooser, save dialogs, and message boxes. Instead of building custom HTML modals that never quite match the platform feel, you tap into the same dialogs users already know from Finder, Explorer, or their Linux desktop.
Tauri v2 ships this functionality through the tauri-plugin-dialog plugin. You can invoke dialogs from your React frontend with JavaScript, or from your Rust backend. Both paths hit the same native windows — the choice comes down to where you want the logic to live.
Plugin architecture:
The dialog API is a separate plugin in Tauri v2. It is not built into the core. You must add the Rust crate and the JavaScript package, then grant explicit permissions before any dialog will appear. The Introduction walks through that setup in isolation.
Setting up the Dialog plugin
Every Tauri v2 plugin follows the same three-part installation: add the Rust dependency, register it in your app, and install the frontend package. Dialogs also require a permission entry in your capability file.
Step 1: Add the Rust crate
Add tauri-plugin-dialog to your src-tauri/Cargo.toml. From the src-tauri directory run:
cargo add tauri-plugin-dialog
The crate version will align with your Tauri 2.x release. Always match the major version of the plugin to your Tauri version.
Step 2: Register the plugin in main.rs
Inside the run function of src-tauri/src/main.rs, call .plugin(tauri_plugin_dialog::init()) before .run().
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
If you have other plugins, the order of .plugin() calls does not matter.
Step 3: Install the JavaScript package
Install the matching frontend package in your React project root:
npm install @tauri-apps/plugin-dialog
This package gives you open, save, message, ask, and confirm from JavaScript.
Step 4: Grant permissions in a capability file
Tauri v2 requires explicit permissions for every IPC call. Open your capability file (by default src-tauri/capabilities/default.json) and add the dialog permissions you need to the permissions array.
{
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm"
]
}
Missing permissions cause silent failures:
If a dialog permission is missing, the call will reject with an error in the browser console — the dialog simply never appears. Use only the permissions your app actually needs; you do not have to enable all of them.
After these four steps, your project can open native dialogs. The remaining sections cover each dialog type with examples you can paste into your React app.
File dialogs
A file dialog lets the user pick one or more existing files from their local file system. You control which file types appear, whether multiple selection is allowed, and where the dialog starts browsing.
Import open from the plugin and call it with an options object. The return value is null if the user cancels, a string for a single file, or a string[] for multiple selections.
import { useState } from "react";
import { open } from "@tauri-apps/plugin-dialog";
function App() {
const [filePath, setFilePath] = useState<string | null>(null);
async function pickImage() {
const selected = await open({
title: "Choose an image",
multiple: false,
filters: [
{
name: "Images",
extensions: ["png", "jpg", "jpeg", "webp"],
},
],
});
if (selected && typeof selected === "string") {
setFilePath(selected);
}
}
return (
<div>
<button onClick={pickImage}>Select Image</button>
{filePath && <p>Selected: {filePath}</p>}
</div>
);
}
export default App;
The filters array controls what the user sees in the file type dropdown. The name is shown to the user; the extensions are checked against actual file extensions without a leading dot. When multiple is true, open resolves to a string[] (or an empty array, never null on cancellation — Tauri v2 always returns null for cancellation, not an empty array; verify). In Tauri v2, cancellation returns null regardless of multiple.
Always handle cancellation:
Calling open returns null when the user presses Cancel or closes the dialog. If you treat null as a file path, your code will crash. Always guard with an if (selected) check before using the result.
Folder dialogs
A folder dialog prompts the user to pick a directory. The mechanics are nearly identical to file dialogs — you set directory: true in the options or call pick_folder instead of pick_file.
Set the directory flag to true. The filter concept does not apply, but you can still provide a title and a defaultPath.
import { open } from "@tauri-apps/plugin-dialog";
async function pickFolder() {
const selected = await open({
directory: true,
multiple: false,
title: "Choose a project folder",
});
if (selected && typeof selected === "string") {
console.log("Folder path:", selected);
}
}
If multiple is true, the user can select several directories at once; the return type becomes string[] on success.
Path scoping is automatic:
When the user picks a file or folder through a dialog, Tauri automatically adds that path to the runtime scope. You can then read or write there without additional permission entries. This scope is temporary and resets when the app restarts. If you need persistent access, pair dialogs with tauri-plugin-persisted-scope.
Save dialogs
A save dialog asks the user where a new or overwritten file should go. Unlike an open dialog, the file does not need to exist yet. The return value is a single path string where your application should write the data.
Call save instead of open. The options are similar: filters to restrict visible file types, title, and defaultPath. The defaultPath can be a full file path; if the directory part exists, the dialog starts there with the suggested filename pre-filled.
import { save } from "@tauri-apps/plugin-dialog";
async function exportReport() {
const filePath = await save({
title: "Export Report",
defaultPath: "report.pdf",
filters: [
{ name: "PDF", extensions: ["pdf"] },
{ name: "Text", extensions: ["txt"] },
],
});
if (filePath) {
// Write your file content to `filePath`
console.log("Save location:", filePath);
}
}
The returned path is a string if the user confirmed, and null if they cancelled. You are responsible for actually writing the file — the dialog only negotiates the path.
Message dialogs
Message dialogs display information, ask yes/no questions, or request confirmations. They are simpler than file dialogs — you provide the message text and an optional title. The dialog renders with the platform's native look, including system icons for warnings and errors.
Message (info)
A single-button informational dialog. It returns nothing; you await it to know the user dismissed the box.
import { message } from "@tauri-apps/plugin-dialog";
async function showInfo() {
await message("Export completed successfully.", {
title: "Export",
kind: "info",
});
}
The kind option accepts "info", "warning", or "error". On macOS and Windows this changes the system icon that accompanies the dialog.
Ask (Yes/No)
An ask dialog presents a question with Yes and No buttons. It resolves to true if the user clicked Yes, false otherwise.
import { ask } from "@tauri-apps/plugin-dialog";
async function deleteItem() {
const confirmed = await ask(
"This action cannot be undone. Delete this item?",
{ title: "Confirm Delete", kind: "warning" }
);
if (confirmed) {
// proceed with deletion
}
}
Confirm (OK/Cancel)
A confirm dialog is similar to ask, but with OK and Cancel buttons. It also resolves to a boolean.
import { confirm } from "@tauri-apps/plugin-dialog";
async function overwriteCheck() {
const ok = await confirm("A file with this name already exists. Overwrite?", {
title: "Overwrite",
kind: "warning",
okLabel: "Overwrite",
cancelLabel: "Keep",
});
return ok;
}
The okLabel and cancelLabel options let you customize the button text, though some platforms may limit how much you can change the default wording.
Blocking dialogs freeze the UI:
All Rust message dialog examples used blocking_show for brevity. In a production app, prefer the async show method inside an async command to keep your window responsive while the dialog is open. The JavaScript API is always async and does not block the browser event loop.
Best practices
Dialogs are straightforward to use, but a few habits will prevent crashes, permission issues, and a subpar user experience.
Grant the fewest permissions possible. If your app only needs to open files, do not add dialog:allow-save or dialog:allow-message. This reduces the attack surface and makes your capability file easier to audit.
Always check for cancellation. Every dialog function that returns a path can return null. Treat that as the user deliberately refusing to proceed. Do not fall through to file operations without a guard.
Prefer the JavaScript API for UI-driven flows. When a user clicks a button to pick a file, handling the dialog directly in the frontend keeps the interaction close to the trigger. Use the Rust API when the dialog is part of a backend process — for example, prompting for a save location after a long computation.
Combine with file system scopes. A path obtained from a dialog is automatically added to the runtime scope, so the File System API readTextFile or writeTextFile will work without extra permission entries. If you need to access that path after an app restart, persist it with tauri-plugin-persisted-scope.
Use the appropriate dialog type for the task. Message dialogs are for one-way information. Ask dialogs are for decisions with consequences. Confirm dialogs are for irreversible actions. Choosing the correct type aligns with platform conventions and reduces user mistakes.
Do not rely on dialogs for security-critical decisions. The dialog API negotiates file paths, not data. A determined attacker can bypass frontend constraints and invoke file operations directly if you do not validate paths on the Rust side. For sensitive operations, write a dedicated Tauri command that verifies the path before acting on it.
Never trust raw dialog paths in security contexts:
The file path returned by a dialog is user-selected, but your app receives it as a string. If you immediately use it in a file operation without checking that it resides inside an expected directory, you open a path traversal vulnerability. Always validate paths against an allowed base directory in your Rust commands.
This page covered every dialog type available in Tauri v2: file open, folder selection, save, and the three message variants. The same native windows are available from JavaScript and Rust; the decision of which to use depends on where your interaction originates. With permissions correctly configured and cancellation handled, dialogs give your React app a polished, platform-native way to interact with the file system.
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.
File Dialogs
How to open native file picker dialogs in a Tauri v2 app using React, covering single-file selection, multiple files, file filters, and default directories
Folder Dialogs
Learn how to implement native folder picker dialogs in a Tauri v2 app using the dialog plugin including single and multiple directory selection from React and Rust
Save Dialogs
Learn how to open native save dialogs in Tauri v2 to let users choose where to save files, with default filenames, file extension filters, and permission configuration.
Message Dialogs
Learn how to show native alert, confirmation, and warning message dialogs in Tauri v2 using the dialog plugin
Best Practices for the Dialog API
Discover best practices for using the Tauri v2 Dialog API to build reliable, user-friendly file and message dialogs with robust validation and error handling.