データベースや利用者の作業データなど、アプリが作るファイルは OS がアプリごとに用意する場所に置きます。フロントエンドは @tauri-apps/api/path の appDataDir() など、Rust は app.path()(PathResolver)の app_data_dir() などで、その OS での正しいパスが得られます。フォルダー名は identifier で決まり、似た関数と同じ場所になるかどうかが OS で違うので、実際の場所と使い分けを表にまとめます。
前提条件
パスを得るだけならプラグインは不要で、path API の権限 core:path:default は core:default に含まれます。得たパスでファイルを読み書きするには File System プラグインを追加します。
npm run tauri add fs
fs:default はアプリ用フォルダーの中の読み取りとフォルダーの作成を許可し、書き込みは含みません。範囲(スコープ)は fs:default が覆っているので、fs:allow-write-text-file を足すだけで書けます。$APPDATA などの変数と関数の対応は ファイルやディレクトリを削除する を参照してください。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
"fs:allow-write-text-file"
]
}
フォルダー名は identifier で決まる
どの関数も「OS の基準フォルダー + identifier」を返します(productName ではありません)。
{
"productName": "My App",
"identifier": "com.example.myapp"
}
- 使えるのは英数字・ハイフン・ピリオドで、ほかのアプリと重ならない逆ドメイン形式にします。
- 公開後に変えると別のフォルダーを見るため、保存済みのデータが消えたように見えます(古いフォルダーは残っています)。
tauri devとインストールした本番版は同じフォルダーを使います。開発中のデータを分けるなら、開発時だけidentifierを変えた設定ファイルを重ねます。
{
"identifier": "com.example.myapp.dev"
}
# 上の JSON を src-tauri/tauri.dev.conf.json として保存し、tauri.conf.json に重ねて起動する
npm run tauri dev -- --config src-tauri/tauri.dev.conf.json
OS ごとの場所と使い分け
<id> は identifier、~ はホームディレクトリです。Rust のメソッドは同じ名前のスネークケース(app_data_dir() など)で、同じ場所を返します。Linux は XDG_DATA_HOME・XDG_CONFIG_HOME・XDG_CACHE_HOME が設定されていればその下になります。
| 関数 | Windows | macOS | Linux |
|---|---|---|---|
appDataDir() | ~\AppData\Roaming\<id> | ~/Library/Application Support/<id> | ~/.local/share/<id> |
appLocalDataDir() | ~\AppData\Local\<id> | ~/Library/Application Support/<id> | ~/.local/share/<id> |
appConfigDir() | ~\AppData\Roaming\<id> | ~/Library/Application Support/<id> | ~/.config/<id> |
appCacheDir() | ~\AppData\Local\<id> | ~/Library/Caches/<id> | ~/.cache/<id> |
appLogDir() | ~\AppData\Local\<id>\logs | ~/Library/Logs/<id> | ~/.local/share/<id>/logs |
tempDir() | ~\AppData\Local\Temp | /var/folders/…/T | /tmp |
homeDir() | C:\Users\<ユーザー名> | /Users/<ユーザー名> | /home/<ユーザー名> |
| 置くもの | 関数 |
|---|---|
| 利用者のデータ(Windows の移動プロファイルでは一緒に移る) | appDataDir() |
| 大きい・この PC 専用のデータ(DB など) | appLocalDataDir() |
| 設定ファイル | appConfigDir() |
| 消えても作り直せるキャッシュ | appCacheDir() |
ログ(Log プラグインの LogDir もここ) | appLogDir() |
| 作業中だけ使う一時ファイル | tempDir() |
表のとおり、OS によっては別の関数が同じフォルダーを指します。同じ名前のファイルを置くとその OS でだけ上書きされるので、db・cache のようなサブフォルダーで分けます。Store プラグインの保存先も appDataDir() です。
1. フロントエンドから実装する (TypeScript)
どの関数も Promise<string> で絶対パスを返します。返るのはパスだけで、フォルダーがあるとは限りません。
import { appCacheDir, appConfigDir, appDataDir, appLocalDataDir, appLogDir } from '@tauri-apps/api/path';
// 5 つのアプリ用フォルダーの場所を並べて出す
export async function logAppDirs(): Promise<void> {
const [data, local, config, cache, log] = await Promise.all([
appDataDir(), appLocalDataDir(), appConfigDir(), appCacheDir(), appLogDir(),
]);
for (const [name, dir] of Object.entries({ data, local, config, cache, log })) {
console.log(name.padEnd(6), dir);
}
}
読み書きでは、絶対パスをつなぐより BaseDirectory.AppData と相対パスを渡す方が簡単です(絶対パスの組み立ては パスを操作する)。アプリ用フォルダーは初回起動では無いことがあるので、書く前に recursive: true で作ります(新しいディレクトリ(フォルダ)を作成する)。
import { BaseDirectory, exists, mkdir, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
const baseDir = BaseDirectory.AppData;
export async function saveNotes(notes: string[]): Promise<void> {
await mkdir('notes', { baseDir, recursive: true }); // $APPDATA 自体が無くても親ごと作る
await writeTextFile('notes/index.json', JSON.stringify(notes), { baseDir });
}
export async function loadNotes(): Promise<string[]> {
if (!(await exists('notes/index.json', { baseDir }))) return []; // 初回起動
return JSON.parse(await readTextFile('notes/index.json', { baseDir })) as string[];
}
2. バックエンドから実装する (Rust)
app.path() を使うには tauri::Manager の use が必要で、各メソッドは tauri::Result<PathBuf> を返します。setup で必要なフォルダーを作ってパスを State に入れておくと、コマンドごとに組み立てずに済みます。
use std::path::PathBuf;
use tauri::Manager;
/// 設定画面などに出すための、アプリ用フォルダーの一覧
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct AppDirs {
data: PathBuf,
local_data: PathBuf,
config: PathBuf,
cache: PathBuf,
log: PathBuf,
}
fn collect_dirs(app: &tauri::AppHandle) -> tauri::Result<AppDirs> {
let p = app.path();
Ok(AppDirs {
data: p.app_data_dir()?,
local_data: p.app_local_data_dir()?,
config: p.app_config_dir()?,
cache: p.app_cache_dir()?,
log: p.app_log_dir()?,
})
}
#[tauri::command]
fn app_dirs(app: tauri::AppHandle) -> Result<AppDirs, String> {
collect_dirs(&app).map_err(|e| e.to_string())
}
/// 起動時に決めた DB ファイルのパス
struct DbPath(PathBuf);
#[tauri::command]
fn db_path(db: tauri::State<'_, DbPath>) -> String {
db.0.display().to_string()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.setup(|app| {
// この PC 専用の大きいデータなので LocalData に置く。フォルダーは自分で作る
let dir = app.path().app_local_data_dir()?.join("db");
std::fs::create_dir_all(&dir)?;
app.manage(DbPath(dir.join("main.sqlite")));
Ok(())
})
.invoke_handler(tauri::generate_handler![app_dirs, db_path])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
type AppDirs = { data: string; localData: string; config: string; cache: string; log: string };
const dirs = await invoke<AppDirs>('app_dirs');
console.log(dirs.localData, await invoke<string>('db_path'));
動作確認
npm run tauri dev で logAppDirs() を呼ぶと、identifier が com.example.myapp の Windows では次のように出ます(tauri.dev.conf.json を重ねると末尾が .dev 付きになります)。
data C:\Users\me\AppData\Roaming\com.example.myapp
local C:\Users\me\AppData\Local\com.example.myapp
config C:\Users\me\AppData\Roaming\com.example.myapp
cache C:\Users\me\AppData\Local\com.example.myapp
log C:\Users\me\AppData\Local\com.example.myapp\logs
saveNotes(['a']) を呼ぶと、Roaming 側の com.example.myapp\notes\index.json ができます。
よくあるエラーと対処法
- 「failed to open file at path: <パス> with error: …」: 書き込み先のフォルダーがありません。
recursive: trueのmkdir()で作ってから書きます。 - 「fs.write_text_file not allowed. Permissions associated with this command: …」: 書き込み権限の追加漏れです(リリースビルドでは「Command plugin:fs|write_text_file not allowed by ACL」)。
EBWebViewの中で「forbidden path: <パス>」になる: Windows の WebView のデータ(localStorage や Cookie)で、fs:defaultが拒否しています。自分のファイルは別のサブフォルダーに置きます。- 更新後にデータが消えた:
identifierを変えていないか確かめます。移行するなら、Rust のsetupでapp.path().data_dir()?.join("旧 identifier")の中身を移します。
OS ごとの違いと注意点
- Windows:
AppDataは隠しフォルダーです。自分で開くときは、パスをエクスプローラーのアドレスバーに貼り付けます。 - macOS: Finder では
~/Libraryが隠れています。データ・ローカルデータ・設定は同じフォルダーです。 - Linux: ローカルデータのフォルダーに、ログと WebView のデータも入ります。
- 共通: アプリ用フォルダーを丸ごと消すと、同じ場所を指す別の用途のファイルや WebView のデータまで消えます。消すのはサブフォルダーの単位にします。
