一時ファイルの掃除やキャッシュの一掃、ユーザーが選んだファイルの削除には、File System プラグインの remove() を使います。ファイルもフォルダーもこの 1 つで消せ、中身のあるフォルダーは recursive: true を付けたときだけ消えます。削除はゴミ箱を通らず元に戻せないので、どこを消してよいかをスコープ(許可するパスの範囲)で絞ることが何より大切です。Rust の std::fs で消す場合はスコープが効かないため、コマンドの側で対象を確かめます。
前提条件
fs プラグインを追加します。ユーザーに選ばせて消す例ではダイアログプラグインも使います。
npm run tauri add fs
npm run tauri add dialog
削除の権限 fs:allow-remove は fs:default に含まれないので追加します。fs プラグインの操作は、コマンドの許可とパスの範囲の両方がそろって初めて通ります。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
"dialog:default",
{
"identifier": "fs:allow-remove",
"allow": [{ "path": "$DOWNLOAD/MyApp/**" }],
"deny": [
{ "path": "$APPDATA" },
{ "path": "$APPDATA/settings.json" },
{ "path": "$APPDATA/db" },
{ "path": "$APPDATA/db/**" }
]
}
]
}
範囲(スコープ)の決まり方
- パスは
$APPDATA・$APPCACHE・$DOWNLOADなどの変数で始めます。BaseDirectory.AppDataが$APPDATAに当たり、実際の場所は OS で変わります(アプリ専用のデータ保存フォルダパスを取得する)。 *は直下の 1 階層、**は下の階層すべてに一致します。フォルダー自体と中身は別の指定で、公式のfs:scope-appdata-recursiveも$APPDATAと$APPDATA/**を並べています。fs:allow-removeの中のallow/denyは削除だけに効きます。fs:scope-download-recursiveのような範囲だけの権限は、fs のすべての操作に効きます。denyはallowより優先されます。fs:defaultは、アプリ用の 5 つのフォルダー($APPCONFIG・$APPDATA・$APPLOCALDATA・$APPCACHE・$APPLOG)とその中身を、すべての操作に効く範囲として含みます。そのためfs:defaultがあれば、fs:allow-removeを足すだけでアプリ用フォルダーの中もフォルダー自体も消せます。
上の例の allow はアプリ用フォルダー以外の場所を足し、deny は消されては困るものを守ります。範囲の判定は渡したパス 1 つにだけ行われ、recursive: true で親フォルダーを消すと deny に書いた中身も一緒に消えるため、$APPDATA 自体も deny に入れています。
ダイアログの open() / save() で選ばれたパスと、ウィンドウにドロップされたファイルは、実行中だけ自動で範囲に加わります。再起動後も覚えておくなら Persisted Scope で権限状態を維持する を使います。
1. フロントエンドから実装する (TypeScript)
ファイル・空のフォルダー・中身ごとのフォルダー
import { remove, BaseDirectory } from '@tauri-apps/plugin-fs';
const baseDir = BaseDirectory.AppCache;
// ファイル。recursive は付けても付けなくても同じ
await remove('cache/thumb-001.png', { baseDir });
// 空のフォルダー。中身があると失敗する(消しすぎを防げる)
await remove('cache/empty', { baseDir });
// フォルダーを中身ごと消す(rm -rf 相当)。元に戻せない
await remove('cache', { baseDir, recursive: true });
パスに空文字列や . を渡すと、baseDir のフォルダーそのものが対象になります。変数が空のまま recursive: true で呼ぶとアプリのデータがまるごと消えるので、呼ぶ前に確かめます。シンボリックリンクはリンクだけが消え、リンク先は残ります。
無ければ何もしない
存在しないパスの remove() は「failed to get metadata of path: …」で始まるエラーになります。「あれば消す」は exists() で確かめてから呼びます。exists() の権限はアプリ用フォルダーなら fs:default に含まれますが、それ以外の場所では fs:allow-exists にも範囲が要ります。
import { exists, remove, BaseDirectory } from '@tauri-apps/plugin-fs';
// 消したら true、もともと無ければ false
export async function removeIfExists(path: string, baseDir: BaseDirectory, recursive = false): Promise<boolean> {
const p = path.trim();
if (p === '' || p === '.' || p === './') throw new Error('refusing to remove the base directory');
if (!(await exists(p, { baseDir }))) return false;
await remove(p, { baseDir, recursive });
return true;
}
ユーザーが選んだファイルを確認してから消す
import { ask, open } from '@tauri-apps/plugin-dialog';
import { remove } from '@tauri-apps/plugin-fs';
export async function pickAndDelete(): Promise<void> {
const file = await open({ multiple: false, directory: false });
if (!file) return; // キャンセル
const ok = await ask(`${file}\nを削除します。ゴミ箱には入らず、元に戻せません。`, {
title: '削除の確認',
kind: 'warning',
});
if (ok) await remove(file); // 選ばれたファイルは範囲に加わっているので allow の追加は不要
}
2. バックエンドから実装する (Rust)
Rust の std::fs には capability もスコープも効きません。受け取ったパスをそのまま remove_dir_all() に渡すコマンドは、JS から何でも消せる穴になります。ファイル名だけを受け取り、消してよいフォルダーの下でパスを組み立てます。ファイルは remove_file()、空のフォルダーは remove_dir()、中身ごとは remove_dir_all() で、対象が無いことは ErrorKind::NotFound で見分けられます。
use std::io::ErrorKind;
use std::path::{Component, Path, PathBuf};
use tauri::Manager;
/// アプリデータの exports フォルダーの中のパスにする。ファイル名 1 つ以外は拒否
fn export_file(app: &tauri::AppHandle, name: &str) -> Result<PathBuf, String> {
let mut parts = Path::new(name).components();
if !matches!((parts.next(), parts.next()), (Some(Component::Normal(_)), None)) {
return Err(format!("invalid file name: {name}"));
}
let dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
Ok(dir.join("exports").join(name))
}
/// 1 ファイルを消す。消したら true、もともと無ければ false
#[tauri::command]
fn delete_export(app: tauri::AppHandle, name: String) -> Result<bool, String> {
let path = export_file(&app, &name)?;
match std::fs::remove_file(&path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == ErrorKind::NotFound => Ok(false),
Err(e) => Err(format!("{}: {e}", path.display())),
}
}
/// キャッシュ専用のサブフォルダーを中身ごと消し、空で作り直す
#[tauri::command]
fn reset_cache(app: tauri::AppHandle) -> Result<(), String> {
let dir = app.path().app_cache_dir().map_err(|e| e.to_string())?.join("cache");
match std::fs::remove_dir_all(&dir) {
Ok(()) => {}
Err(e) if e.kind() == ErrorKind::NotFound => {}
Err(e) => return Err(format!("{}: {e}", dir.display())),
}
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![delete_export, reset_cache])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
const removed = await invoke<boolean>('delete_export', { name: 'report-2026-09.csv' });
console.log(removed ? '削除しました' : 'もともとありませんでした');
await invoke('delete_export', { name: '../settings.json' }).catch((e) => console.error(e));
await invoke('reset_cache');
動作確認
npm run tauri dev で起動し、removeIfExists('cache', BaseDirectory.AppCache, true) を 2 回呼ぶと true、false の順に返ります。deny に入れた settings.json の remove() は失敗し、ファイルは残ります。
true
false
forbidden path: C:\Users\me\AppData\Roaming\com.example.app\settings.json
Rust の delete_export に ../settings.json を渡すと「invalid file name: ../settings.json」で拒否されます。
よくあるエラーと対処法
- 「fs.remove not allowed. Permissions associated with this command: fs:allow-app-write, …」:
fs:allow-removeの追加漏れです(この詳しい文面は開発ビルドのもので、リリースビルドでは「Command plugin:fs|remove not allowed by ACL」)。追加してtauri devを再起動します。 - 「forbidden path: <パス>, maybe it is not allowed on the scope for
allow-removepermission in your capability file」: パスが範囲の外です。変数、*と**の違い、baseDirの指定漏れを見直します。denyに一致した場合と、リリースビルドでは「forbidden path: <パス>」だけになります。 - 「failed to remove path: …」: 中身のあるフォルダーを
recursiveなしで消そうとしたか、ほかのアプリが使っている、書き込み権が無いなどで OS が拒否しました。続く OS のメッセージで見分けます。 - 「failed to get metadata of path: …」: 消す対象がありません。
exists()で確かめるか、このエラーを無視します。
OS ごとの違いと注意点
アプリ用の変数には、OS によって同じフォルダーを指すものがあります。フォルダーの中身を丸ごと消すと別の用途のファイルまで消えるので、キャッシュは cache のような専用のサブフォルダーに置き、消すのもそこだけにします。
- Windows:
$APPCACHEと$APPLOCALDATAは同じフォルダーで、$APPLOGはその中のlogs、WebView のデータ(EBWebView)もここにあります。 - macOS:
$APPCONFIG・$APPDATA・$APPLOCALDATAはどれも~/Library/Application Support/<identifier>です。 - Linux:
$APPDATAと$APPLOCALDATAは同じフォルダーで、$APPLOGと WebView のデータもその中です。 - macOS / Linux: 範囲の
*と**は.で始まる名前(隠しファイル)に一致しません。.cacheなどを消すなら範囲に.から書くか、tauri.conf.jsonのplugins.fs.requireLiteralLeadingDotをfalseにします。 - 共通: fs プラグインにはゴミ箱へ移す機能はありません。移動や改名で「消したように見せる」なら ファイル名を変更する・別の場所へ移動する を使います。
