# How to Automatically Open Downloaded Files Using Tauri: A Complete Guide

> Learn how to automatically open downloaded files with Tauri. This guide explains using WebviewBuilder and Shell plugin to launch files using system defaults.

- Repository: [lencx/ChatGPT](https://github.com/lencx/ChatGPT)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Use Tauri's `WebviewBuilder::on_download` callback to capture the `DownloadEvent::Finished` event, then invoke `app_handle.shell().open()` from the Shell plugin to launch the file with the system's default application.**

The **lencx/ChatGPT** desktop application demonstrates this pattern to provide a seamless native experience when users download files from the embedded webview. By handling download events in the Tauri backend, you can automatically open downloaded files using Tauri without requiring manual user navigation to the downloads folder.

## Understanding Tauri's Download Event Lifecycle

Tauri's webview emits a sequence of events during the download process. To automatically open downloaded files, you must intercept these events and persist the target file path until the download completes.

### Capturing Download Requests with WebviewBuilder

The `WebviewBuilder::on_download` method registers a closure that receives every `DownloadEvent`. When a `DownloadEvent::Requested` occurs, you should resolve the user's download directory and store the destination path.

In the ChatGPT application, this logic resides in [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs):

```rust
// src-tauri/src/core/setup.rs
.on_download({
    let app_handle = app_handle.clone();
    let download_path = Arc::new(Mutex::new(PathBuf::new()));
    
    move |_webview, event| {
        match event {
            DownloadEvent::Requested { destination, .. } => {
                let download_dir = app_handle
                    .path()
                    .download_dir()
                    .expect("Failed to get download dir");
                let mut locked = download_path.lock().expect("Lock failed");
                *locked = download_dir.join(&destination);
                *destination = locked.clone();
            }
            // ...
        }
        true
    }
})

```

### Handling the DownloadEvent::Finished Callback

When the `DownloadEvent::Finished` event fires with `success: true`, you retrieve the stored path and invoke the Shell plugin's `open` method. This is the critical step that triggers the operating system to launch the file with its associated default application.

From `src-tauri/src/core/setup.rs:81-85`:

```rust
// src-tauri/src/core/setup.rs:81‑85
if success {
    app_handle
        .shell()
        .open(final_path.to_string_lossy(), None)
        .expect("[view:download] Failed to open file");
}

```

## Implementing Automatic File Opening in ChatGPT Desktop

The ChatGPT desktop application (`lencx/ChatGPT`) implements this pattern to handle downloads from the ChatGPT web interface. The implementation requires the **Tauri Shell plugin** and careful management of the download state.

### The Setup Logic in src-tauri/src/core/setup.rs

The core implementation resides in the `setup` function within [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs). This file initializes the main webview and configures the download handler:

- **Lines 56-90**: Register the `on_download` callback during webview construction
- **Path resolution**: Uses `app_handle.path().download_dir()` to determine the user's default downloads folder
- **State management**: Uses `Arc<Mutex<PathBuf>>` to share the download path between the request and finish events

### Using the Shell Plugin to Open Files

The **Shell plugin** (`tauri_plugin_shell`) provides the `ShellExt` trait, which adds the `shell()` method to `AppHandle`. This method returns a `Shell` instance with an `open` method that accepts:

- **Path**: The file path as a string (using `to_string_lossy()` for cross-platform compatibility)
- **OpenOptions**: Optional configuration (pass `None` for default behavior)

This abstraction works across **macOS**, **Windows**, and **Linux**, automatically invoking the appropriate system command (`open` on macOS, `start` on Windows, `xdg-open` on Linux).

## Complete Code Example for Tauri Auto-Open Downloads

Here is the complete implementation pattern based on the ChatGPT desktop application source code:

```rust
use tauri::{
    webview::{DownloadEvent, WebviewBuilder, WebviewUrl},
    Manager, AppHandle,
};
use tauri_plugin_shell::ShellExt;
use std::sync::{Arc, Mutex};
use std::path::PathBuf;

fn setup_webview(app_handle: AppHandle) {
    // Shared state to store the download path between events
    let download_path = Arc::new(Mutex::new(PathBuf::new()));
    
    let main_view = WebviewBuilder::new("main", WebviewUrl::App("https://example.com".into()))
        .auto_resize()
        .on_download({
            let app_handle = app_handle.clone();
            let download_path = download_path.clone();
            
            move |_webview, event| {
                match event {
                    DownloadEvent::Requested { destination, .. } => {
                        // Resolve the user's download directory
                        let download_dir = app_handle
                            .path()
                            .download_dir()
                            .expect("Failed to get download dir");
                        
                        // Store the full path for later use
                        let mut locked = download_path.lock().expect("Lock failed");
                        *locked = download_dir.join(&destination);
                        *destination = locked.clone();
                    }
                    DownloadEvent::Finished { success, .. } => {
                        if success {
                            // Retrieve the stored path
                            let path = {
                                let lock = download_path.lock().expect("Lock failed");
                                lock.clone()
                            };
                            
                            // Open the file with the default application
                            app_handle
                                .shell()
                                .open(path.to_string_lossy(), None)
                                .expect("Failed to open the downloaded file");
                        }
                    }
                    _ => {}
                }
                true // Keep the download handler active
            }
        })
        .build()
        .expect("Failed to build webview");
}

```

## Cross-Platform Considerations

The Shell plugin abstracts platform differences, but you should consider these implementation details:

- **Path handling**: Use `to_string_lossy()` on `PathBuf` when passing to `shell().open()` to handle non-UTF8 paths gracefully on Windows.
- **Download directory**: Always resolve `app_handle.path().download_dir()` rather than hardcoding paths, as this respects the user's system preferences on all platforms.
- **Error handling**: The `expect` calls in the example should be replaced with proper error logging in production applications to prevent panics.

## Summary

- **Tauri's `on_download` callback** captures download lifecycle events from the embedded webview.
- **`DownloadEvent::Requested`** allows you to intercept the destination path and redirect it to the user's download directory.
- **`DownloadEvent::Finished`** signals completion, providing the final file path and success status.
- The **Shell plugin's `open` method** (`app_handle.shell().open()`) launches the file with the system's default application.
- This pattern is implemented in **[`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs)** within the **lencx/ChatGPT** repository.

## Frequently Asked Questions

### How does Tauri detect when a file download completes?

Tauri emits a `DownloadEvent::Finished` event through the `WebviewBuilder::on_download` callback. This event includes a `success` boolean and the final file path. In the ChatGPT desktop app, this is handled in [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs) to trigger the file opening logic.

### What Tauri plugin is required to open files with default applications?

You need the **Tauri Shell plugin** (`tauri_plugin_shell`). It provides the `ShellExt` trait which adds the `shell()` method to `AppHandle`, allowing you to call `open(path, None)` to launch files with their associated default programs on macOS, Windows, and Linux.

### Can I customize which application opens the downloaded file?

Yes. While the example uses `None` for default application behavior, the `open` method accepts an `OpenOptions` parameter where you can specify a custom program. However, for cross-platform compatibility, relying on the system's default association via `None` is generally preferred unless you have specific application requirements.

### Is this automatic file opening behavior available on all platforms?

Yes. The implementation works on **macOS**, **Windows**, and **Linux** because the Shell plugin abstracts the platform-specific commands (`open` on macOS, `start` on Windows, `xdg-open` on Linux). The code in [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs) uses standard Tauri APIs that are fully cross-platform.