Working with Paths in Tauri v2

Learn to join, resolve, normalize, and extract parts of file paths safely across platforms using the Tauri v2 Path API from a React frontend.

The path utilities in @tauri-apps/api/path let you build, clean, and inspect file paths without worrying about the differences between Windows, macOS, and Linux. Hand‑stitching strings like base + "/" + file breaks the moment your app runs on a different operating system. Tauri’s API gives you functions that always produce correctly separated, valid paths, so you can focus on what those paths point to rather than how they are spelled.

Platform separators differ:

Windows uses backslashes (\), while macOS and Linux use forward slashes (/). Building paths with string addition or template literals will produce broken paths on at least one platform. Always use the API functions instead.

Joining path segments with join

join takes any number of string segments and stitches them together using the correct separator for the current operating system. It also normalises the result, so double separators or trailing slashes do not slip through.

import { join } from "@tauri-apps/api/path";
async function buildConfigPath() {
  const base = await join("C:\\Users\\Dev", "app-data");
  const full = await join(base, "config", "settings.json");
  console.log(full);
}

On Windows you would see C:\Users\Dev\app-data\config\settings.json. On macOS or Linux, given a Unix‑style base like /home/dev, you would see /home/dev/app-data/config/settings.json.

The key detail is that join does not care whether the segments you give it already contain separators. It removes redundant slashes and backslashes automatically. This protects you from bugs like C:\Users\Dev\\app-data that occur when you manually concatenate strings and one of them already ends with a slash.

Do not build paths with template literals:

Writing `${base}/${subdir}` forces forward slashes even on Windows. It also leaves the door open for double separators if base already ends with a slash. Such paths can fail silently or cause file‑system errors that are difficult to diagnose.

Turning relative paths into absolute ones with resolve

A relative path like "config/settings.json" is meaningless unless you know where it starts. resolve takes a list of segments and an optional base directory, and returns an absolute path.

import { resolve, BaseDirectory } from "@tauri-apps/api/path";
async function getAppConfigPath() {
  const absPath = await resolve("config/settings.json", {
    baseDir: BaseDirectory.AppConfig,
  });
  console.log(absPath);
}

BaseDirectory.AppConfig tells resolve to anchor the path in the application’s configuration directory — the one Tauri calculates from the operating system’s conventions and your app’s bundle identifier. The result will look like:

  • Windows: C:\Users\You\AppData\Roaming\com.yourapp\config\settings.json
  • macOS: /Users/You/Library/Application Support/com.yourapp/config/settings.json
  • Linux: /home/you/.config/com.yourapp/config/settings.json

If you omit baseDir, the path is resolved relative to the current working directory of the running app, which is almost never what you want for storing persistent data.

Correct base directory guarantees portability:

Using BaseDirectory members ensures your files land in the platform‑correct location without you hard‑coding AppData, Library, or ~/.config. Your application code stays identical across operating systems.

Cleaning up redundant segments with normalize

When paths come from user input, configuration files, or external systems, they often contain .., ., or repeated separators. normalize removes those and returns the clean, canonical form — without touching the file system.

import { normalize } from "@tauri-apps/api/path";
async function cleanUserPath(raw: string) {
  const clean = await normalize(raw);
  console.log(clean);
}
cleanUserPath("/home/dev/../dev//projects/./app");

The output is /home/dev/projects/app. The .. pops up one directory, the . is ignored, and the double slash collapses to one.

normalize does not check if the path exists:

normalize is purely a string operation. It will happily produce a path that points to nothing. Use it when you need a predictable, clean string to store or compare, but do not rely on it to validate that a file or folder actually exists.

Extracting parts of a path

Sometimes you need only the extension, the file name, or the containing directory from a full path. Tauri provides extname, basename, and dirname that work cross‑platform.

import { extname, basename, dirname } from "@tauri-apps/api/path";
async function inspectPath() {
  const filePath = "C:\\Users\\Dev\\documents\\report.pdf";
  const ext = await extname(filePath);      // ".pdf"
  const base = await basename(filePath);    // "report.pdf"
  const dir = await dirname(filePath);      // "C:\\Users\\Dev\\documents"
  console.log({ ext, base, dir });
}

These functions operate on the string itself, exactly like their Node.js path module counterparts. They do not need the file to exist. An important edge case: on Windows, extname("archive.tar.gz") returns ".gz", not ".tar.gz" — only the last dot‑separated segment is treated as the extension. This matches conventional file‑system behaviour and the behaviour developers expect from Node.js.

Double extensions are not detected:

If your app uses compound extensions like .tar.gz or .config.json, extname will return only the final part. You would need to write additional logic to detect known compound patterns.

Practical walkthrough — building a safe path to store user data

The following example ties together resolve, join, and normalize in a realistic flow. Suppose you want to store user‑generated notes in a subdirectory inside the app’s data folder, and you want to accept a notebook name the user typed.

1

Get the app data directory

Start with the platform‑correct base directory for persistent data.

import { resolve, BaseDirectory } from "@tauri-apps/api/path";
const dataDir = await resolve("", {
  baseDir: BaseDirectory.AppData,
});
2

Join the notebook subdirectory

Build a subfolder path from the user input. Never trust raw input directly — always run it through join to avoid separator issues.

import { join } from "@tauri-apps/api/path";
const notebookName = "travel-notes";
const notebookDir = await join(dataDir, notebookName);
3

Normalize the final result

The input might contain odd characters or dots. Normalize to guarantee a clean, predictable string before you pass it to file‑system APIs.

import { normalize } from "@tauri-apps/api/path";
const safePath = await normalize(notebookDir);
console.log(safePath);
4

Use the path with Tauri’s file system plugin

Now that you have a clean absolute path, you can create the directory via the file system plugin.

import { mkdir } from "@tauri-apps/plugin-fs";
await mkdir(safePath, { recursive: true });
console.log("Notebook directory ready:", safePath);

Your path is platform‑safe:

If your console shows something like C:\Users\...\com.yourapp\travel-notes on Windows or /home/.../.local/share/com.yourapp/travel-notes on Linux, the path API has done its job correctly. The same code runs everywhere.

Summary

The Tauri path API removes every reason to manually splice directories and file names. The three functions you will reach for most are join to build segments, resolve to anchor a relative path to the correct system directory, and normalize to strip out redundant separators and dots. When you need only a piece of a path, extname, basename, and dirname do the parsing without touching the disk.