Tauri is a framework for building secure, small desktop applications by combining a web frontend with native system capabilities. It exposes a number of native features to the web application layer, including:
These native capabilities make it possible to build web-based desktop apps that still feel well integrated with the local operating system.
This article focuses on one practical file-related example: implementing file download behavior for an image-slicing tool.
Before wiring up native storage support, assume the application already has a page like this: an image-slicing tool where users upload an image and export sliced output files.
In a browser, file download often looks like this:
const downloadSlice = (sliceData: any, fileName: string) => {
const link = document.createElement("a");
link.download = fileName;
link.href = sliceData;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};That approach works in normal browser environments because it relies on the browser's <a> element download behavior.
In Tauri, however, it is better to use native capabilities. In this scenario the main modules involved are:
pathdialogfsYou also need to enable the relevant permissions in Tauri config before these APIs can be used.
path To enable all path APIs:
"path": {
"all": true
}dialog The dialog allowlist includes options such as:
opensavemessageaskconfirmFor this example, everything is enabled:
"dialog": {
"all": true,
"ask": true,
"confirm": true,
"message": true,
"open": true,
"save": true
}fs For the filesystem, the example enables all APIs and allows access to all files:
"fs": {
"all": true,
"scope": ["**"]
}After that, the app is ready to use native file APIs.
Because this feature needs to work in both browser and Tauri environments, the runtime is detected first:
if (window.__TAURI__) {
// Tauri environment
} else {
// browser environment
}Because the image-slicing tool exports many images at once, ZIP packaging is also useful.
The high-level flow looks like this:
Implementation:
import { writeBinaryFile } from "@tauri-apps/api/fs";
import { path, dialog } from "@tauri-apps/api";
import JSZip from "jszip";
import dayJS from "dayjs";
import { FileBase64List } from "@/type";
export const downloadFileBase64 = async (data: any, fileName: string) => {
if (window.__TAURI__) {
const binaryString = atob(data.split(",")[1]);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
try {
const basePath = await path.downloadDir();
let selPath: any = await dialog.save({
defaultPath: basePath,
});
selPath = selPath.replace(/Untitled$/, "");
writeBinaryFile({ contents: bytes, path: `${selPath}${fileName}` });
} catch (error) {
console.error(error);
}
} else {
const link = document.createElement("a");
link.download = fileName;
link.href = data;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
};
export const downloadBase64FileWithZip = async (
data: FileBase64List,
callback: Function
) => {
const zip = new JSZip();
data.forEach((item) => {
zip.file(item.name, item.data.replace(/^data:image\/(png|jpg);base64,/, ""), {
base64: true,
});
});
const date = dayJS().format("YYYYMMDDHHmmss");
const basePath = await path.downloadDir();
let selPath: any = await dialog.save({
defaultPath: basePath,
});
selPath = selPath.replace(/Untitled$/, "");
zip.generateAsync({ type: "blob" }).then((content) => {
let file = new FileReader();
file.readAsArrayBuffer(content);
file.onload = function (e: any) {
let fileU8A = new Uint8Array(e.target.result);
writeBinaryFile({ contents: fileU8A, path: `${selPath}IMG_${date}.zip` });
callback();
};
});
};
export const downloadFileBase64List = async (
data: FileBase64List,
callback: Function
) => {
if (window.__TAURI__) {
downloadBase64FileWithZip(data, callback);
} else {
data.forEach((item) => {
downloadFileBase64(item.data, item.name);
});
}
};This example covers a practical native-capability workflow in Tauri: using the file system, dialogs, and path APIs to implement reliable file download behavior.
It is also a good introduction to how Tauri permissions, native modules, and runtime branching work together.
Tauri's built-in native capabilities are powerful, but there are limits. If you need more advanced behavior, you may eventually move into IPC and custom native extensions. Even so, the file download example already captures the basic pattern for connecting a web UI to the native desktop layer.
https://github.com/Xutaotaotao/XTools/blob/feature/tauri/src/pages/imageSlicing/index.tsx