Tauri Path Handling
Context Detection
Before choosing a path API, determine your execution context:
| Context | Location | Correct API |
| ----------------------- | ---------------------------------------------- | ---------------------- |
| Tauri frontend | apps/*/src/**/*.ts, apps/*/src/**/*.svelte | @tauri-apps/api/path |
| Node.js/Bun backend | packages/**/*.ts, CLI tools | Node.js path module |
Rule: If the code runs in the browser (Tauri webview), use Tauri's path APIs. If it runs in Node.js/Bun, use the Node.js path module.
Available Functions from @tauri-apps/api/path
Path Manipulation
| Function | Purpose | Example |
| ---------------------- | ------------------------------------------ | ------------------------------------------------- |
| join(...paths) | Join path segments with platform separator | await join(baseDir, 'workspaces', id) |
| dirname(path) | Get parent directory | await dirname('/foo/bar/file.txt') → /foo/bar |
| basename(path, ext?) | Get filename, optionally strip extension | await basename('/foo/bar.txt', '.txt') → bar |
| extname(path) | Get file extension | await extname('file.txt') → .txt |
| normalize(path) | Resolve .. and . segments | await normalize('/foo/bar/../baz') → /foo/baz |
| resolve(...paths) | Resolve to absolute path | await resolve('relative', 'path') |
| isAbsolute(path) | Check if path is absolute | await isAbsolute('/foo') → true |
Platform Constants
| Function | Purpose | Returns |
| ------------- | ----------------------- | ---------------------------- |
| sep() | Platform path separator | \ on Windows, / on POSIX |
| delimiter() | Platform path delimiter | ; on Windows, : on POSIX |
Base Directories
| Function | Purpose |
| ----------------------- | ---------------------------------- |
| appLocalDataDir() | App's local data directory |
| appDataDir() | App's roaming data directory |
| appConfigDir() | App's config directory |
| appCacheDir() | App's cache directory |
| appLogDir() | App's log directory |
| tempDir() | System temp directory |
| resourceDir() | App's resource directory |
| resolveResource(path) | Resolve path relative to resources |
Patterns
Constructing Paths (Correct)
import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';
// Join path segments - handles platform separators automatically
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'workspaces', workspaceId, 'data.json');
// Get parent directory - cleaner than manual slicing
const parentDir = await dirname(filePath);
await mkdir(parentDir, { recursive: true });
Logging Paths (Exception)
For human-readable log output, hardcoded / is acceptable since it's not used for filesystem operations:
// OK for logging - consistent cross-platform log output
const logPath = pathSegments.join('/');
console.log(`[Persistence] Loading from ${logPath}`);
Anti-Patterns
Never: Manual String Concatenation
// BAD: Hardcoded separator breaks on Windows
const filePath = baseDir + '/' + 'workspaces' + '/' + id;
// BAD: Template literal with hardcoded separator
const filePath = `${baseDir}/workspaces/${id}`;
// GOOD: Use join()
const filePath = await join(baseDir, 'workspaces', id);
Never: Manual Parent Directory Extraction
// BAD: Manual slicing is error-prone
const parentSegments = pathSegments.slice(0, -1);
const parentDir = await join(baseDir, ...parentSegments);
// GOOD: Use dirname()
const parentDir = await dirname(filePath);
Never: Hardcoded Separators in Filesystem Operations
// BAD: Windows uses backslashes
const configPath = appDir + '/config.json';
// GOOD: Platform-agnostic
const configPath = await join(appDir, 'config.json');
Never: Assuming Path Format
// BAD: Splitting on '/' fails on Windows paths
const parts = filePath.split('/');
// GOOD: Use dirname/basename for extraction
const dir = await dirname(filePath);
const file = await basename(filePath);
Import Pattern
Always import from @tauri-apps/api/path:
import {
appLocalDataDir,
dirname,
join,
basename,
extname,
normalize,
resolve,
sep,
} from '@tauri-apps/api/path';
Note on Async
All Tauri path functions are async because they communicate with the Rust backend via IPC. Always await them:
// All path operations return Promises
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'file.txt');
const parent = await dirname(filePath);
const separator = await sep();
Filesystem Operations
Use @tauri-apps/plugin-fs for file operations, combined with Tauri path APIs:
import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';
import { mkdir, readFile, writeFile } from '@tauri-apps/plugin-fs';
async function saveData(segments: string[], data: Uint8Array) {
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, ...segments);
// Ensure parent directory exists
const parentDir = await dirname(filePath);
await mkdir(parentDir, { recursive: true });
await writeFile(filePath, data);
}