Opening External Resources
Learn how to open URLs, files, and folders in their default applications from a Tauri v2 application using the Shell plugin.
Desktop applications often need to hand off content to the operating system — opening a link in the default browser, showing a downloaded file, or revealing a folder in the native file manager. Tauri’s Shell plugin provides the open function that does exactly this. It acts as a bridge between your app’s sandboxed webview and the user’s desktop environment, launching the right application for a given URL or file path.
This page covers how to configure the Shell plugin, request the required permissions, and use open to work with URLs, files, and directories. All examples assume a React + Vite frontend and Tauri v2. If the plugin is not installed yet, start with the Shell API Introduction.
Setting Up the Shell Plugin
The open function lives in the @tauri-apps/plugin-shell package. You need to install it on both the JavaScript and Rust sides before using it.
Step 1: Install the JavaScript package
Run the install command for your package manager in the project root.
npm add @tauri-apps/plugin-shell
# or
pnpm add @tauri-apps/plugin-shell
# or
yarn add @tauri-apps/plugin-shell
Step 2: Register the Rust plugin
In src-tauri/src/lib.rs, import the plugin’s init function and attach it to the Tauri builder.
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Step 3: Grant the open permission in capabilities
The Shell plugin’s open function requires an explicit permission. Add "shell:allow-open" to the permissions array in your capability file (usually src-tauri/capabilities/default.json).
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open"
]
}
Setup complete:
If you have completed all three steps, the open function is ready to call from your frontend code.
Opening a URL in the Default Browser
Links to external websites should open in the user’s system browser, not inside the Tauri webview. The open function accepts a URL string and launches the default browser with it.
import { open } from "@tauri-apps/plugin-shell";
function App() {
const handleOpenDocs = () => {
open("https://v2.tauri.app");
};
return (
<div>
<button onClick={handleOpenDocs}>Open Tauri Docs</button>
</div>
);
}
export default App;
The call to open is asynchronous, but you can fire‑and‑forget it from a click handler without awaiting if you don’t need to know the result. When the button is clicked, Tauri sends the URL to the operating system, which delegates it to the application registered for the https protocol — typically a web browser.
Do not use window.open:
Using window.open("https://example.com") from your frontend code attempts to open the URL inside the Tauri webview, not the external browser. Always use the Shell plugin’s open function for external destinations.
Opening a File with Its Default Application
Passing a file path to open tells the OS to launch the application associated with that file type. A PDF opens in the user’s PDF viewer, a .txt file in the default text editor, and so on.
To demonstrate this, you need a valid file path. The following example uses the @tauri-apps/plugin-dialog plugin to let the user pick a file first, then opens it. The same pattern works with paths obtained from the Path API or hard‑coded resource paths.
import { open } from "@tauri-apps/plugin-shell";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
function FileOpener() {
const handleOpenFile = async () => {
const selected = await openDialog({
multiple: false,
title: "Choose a file to open",
});
if (selected) {
await open(selected);
}
};
return <button onClick={handleOpenFile}>Open a File</button>;
}
export default FileOpener;
The dialog plugin returns an absolute path string like C:\Users\Name\Documents\report.pdf on Windows or /home/name/Documents/report.pdf on Linux. That string is then handed to open, which looks up the file extension and launches the matching program.
Paths must be absolute:
If you pass a relative path to open, the operating system may fail to locate the file. Always resolve relative paths to absolute ones before calling open, for example by using app.path().resolve() from Rust or by joining against a known base directory.
The JavaScript open function expects a string path and returns a Promise<void>.
import { open } from "@tauri-apps/plugin-shell";
await open("/absolute/path/to/document.pdf");
Opening a Folder in the File Manager
When you pass a directory path to open, the OS opens the native file manager (Explorer on Windows, Finder on macOS, or the default file browser on Linux) and selects that folder. This is useful for “Show in Folder” buttons after downloading a file or exporting data.
import { open } from "@tauri-apps/plugin-shell";
import { resolve } from "@tauri-apps/api/path";
function ShowFolder() {
const handleShowDownloadDir = async () => {
const downloadDir = await resolve("~/Downloads");
await open(downloadDir);
};
return <button onClick={handleShowDownloadDir}>Open Downloads Folder</button>;
}
export default ShowFolder;
The resolve function from @tauri-apps/api/path converts a path like ~/Downloads into the absolute system‑specific path. After that, open hands the directory path to the OS, which spawns the file manager window.
Cross‑platform behavior:
On Linux, the exact file manager launched depends on the user’s desktop environment. The behavior is the same as double‑clicking a folder in the system’s file browser.
Restricting Which URLs Can Be Opened
By default, the shell:allow-open permission lets your app open any URL or path. In a security‑sensitive application you may want to restrict the open function to only a specific set of URLs — for example, only links under your own domain.
You can scope the permission by replacing the simple string "shell:allow-open" with an object that defines a scope array. Each entry in the scope specifies a URL pattern that is either allowed or denied.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "shell:allow-open",
"allow": [
{ "url": "https://your-domain.com/**" }
],
"deny": [
{ "url": "https://your-domain.com/admin/**" }
]
}
]
}
With this configuration, any call to open with a URL that does not match the allowed pattern or that matches a denied pattern will throw an error. The same scoping mechanism works for file paths; you can use a validator pattern to restrict which paths can be opened.
Test your scope:
If you define a restrictive scope, verify that all intended URLs still open correctly during development. A pattern that is too narrow can silently break functionality after a production build.
Common Mistakes and Debugging
- Missing
shell:allow-openpermission — Theopenfunction will throw a runtime error if the permission is not granted in any capability file attached to the window. The error message usually includes “permission not granted”. - Using
window.openin the frontend — This opens the link inside the Tauri webview, which is almost never what you want for external resources. It also bypasses Tauri’s permission checks. - Relative paths on the file system — On some platforms, passing a relative path like
./data/report.pdfmay resolve against the application’s working directory, which differs between development and production. Always convert to an absolute path. - Not installing the JavaScript package — If your bundler cannot resolve
@tauri-apps/plugin-shell, ensure you ran the install command and that the package appears inpackage.json.
Summary
The Shell plugin’s open function is the single entry point for handing off URLs, files, and folders to the operating system. After a one‑time setup — installing the package, registering the Rust plugin, and adding the permission — you can call open from any part of your frontend or backend code.
The same permission system that makes open work also lets you precisely control which external resources your app can launch, which is critical for applications that need to enforce navigation boundaries.