Search...Search plugins and themes...
⌘K
Sign in
  • Get started
  • Download
  • Pricing
  • Enterprise
  • Account
  • Obsidian
  • Overview
  • Sync
  • Publish
  • Canvas
  • Mobile
  • Web Clipper
  • CLI
  • Learn
  • Help
  • Developers
  • Changelog
  • About
  • Roadmap
  • Blog
  • Resources
  • System status
  • License overview
  • Terms of service
  • Privacy policy
  • Security
  • Community
  • Plugins
  • Themes
  • Discord
  • Forum / 中文论坛
  • Merch store
  • Brand guidelines
Follow us
DiscordTwitterBlueskyThreadsMastodonYouTubeGitHub
© 2026 Obsidian

File Undo

Jeff1024Jeff1024169 downloads

Undo recent file deletions and moves (renames).

Add to Obsidian
  • Overview
  • Scorecard
  • Updates2

Obsidian plugin (manifest.json: id file-undo, name File Undo, v1.0.1, minAppVersion 0.15.0, isDesktopOnly false). Sole runtime is main.js exporting A1FileUndoPlugin. Records vault rename/move and delete (files and folders; listeners do not filter by type) into in-memory undoStack / redoStack and restores them from the command palette. The only persisted key is maxHistory (default 200) in data.json. onload always resets both stacks and ignoreEventsUntil; history does not survive reload or disable. No onunload.

Scope Boundaries

Component Responsible For MUST NOT Contain
A1FileUndoPlugin (main.js) loadSettings / saveSettings; stacks and ignoreEventsUntil; vault listeners; commands; restore I/O Editor/content undo; Obsidian trash-mode policy; UI beyond Notice; persisting stacks
A1FileUndoSettingTab (main.js) Tab "A1 File Undo Settings"; clamp maxHistory 10–999; persist; FIFO-trim undoStack Trimming redoStack; vault rename/delete; restore
Vault rename / delete handlers (onload) Record payloads; skip .trash/ and an open ignore window; redoStack = []; FIFO-trim undo Performing restore

Key Invariants

  1. undoLastAction / redoLastAction assign ignoreEventsUntil = Date.now() + 1000 in finally after awaited I/O, not before. A vault.rename restore can emit rename during the await; that event is skipped only if a previous window is still open. (Why chosen over wrapping I/O or source-tagged events: Obsidian vault events do not distinguish plugin vs user origin; this file sets the flag in finally.)
  2. Record never pushes paths under .trash/ (rename also checks oldPath). Delete restore uses adapter path .trash/ + action.name (basename), not the original full path. (Why chosen over treating trash as a first-class action or storing a unique trash location: .trash/ is the restore source, and the recorder never saves a trash path.)
  3. Any recorded user rename/delete sets redoStack = []. Settings onChange trims undoStack only. (Why chosen over a branching history or trimming both stacks: linear undo/redo matches editor semantics; redo is not a FIFO log.)

Numbered Data Flow

  1. onload() → loadSettings() (Object.assign({}, DEFAULT_SETTINGS, await this.loadData())); undoStack = [], redoStack = [], ignoreEventsUntil = 0; addSettingTab(A1FileUndoSettingTab); registerEvent on app.vault rename / delete; addCommand ×3; console.log("A1 File Undo Plugin loaded!").
  2. Rename listener (file, oldPath): return if Date.now() < ignoreEventsUntil or file.path / oldPath starts with .trash/; else push {type:'rename', newPath:file.path, oldPath}, redoStack = [], shift() while length > settings.maxHistory.
  3. Delete listener (file): same ignore; skip if file.path starts with .trash/; push {type:'delete', path:file.path, name:file.name}; clear redo; FIFO-trim.
  4. Empty undo/redo: Notice and return (no ignore window). Otherwise pop first, then I/O. Missing file/trash: Notice; the popped action is not re-queued; finally still opens the 1s window.
  5. Rename undo: getAbstractFileByPath(action.newPath) then vault.rename(file, action.oldPath). Delete undo: if adapter.exists(".trash/"+action.name), mkdir missing parent of action.path, adapter.rename trash → original path. Success: redoStack.push(action) + Notice. catch / finally same as redo (step 6).
  6. Rename redo: getAbstractFileByPath(action.oldPath) then vault.rename(file, action.newPath). Delete redo: if adapter.exists(action.path), adapter.rename to .trash/+action.name (does not mkdir .trash). Success: undoStack.push(action) + Notice. catch: console.error + Notice. finally: ignoreEventsUntil = Date.now() + 1000.
  7. dump-commands: Node fs.writeFileSync of app.commands.commands to commands_dump.txt at adapter.getBasePath().

Side-effects API

Method Signature Side-Effects
onload async onload() Load settings; reset stacks/ignore; register events/commands/tab; console.log
loadSettings async loadSettings() this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
saveSettings async saveSettings() await this.saveData(this.settings) (settings only, never stacks)
A1FileUndoSettingTab.display display() Build UI. Mutations in text onChange: parseInt (NaN→200), clamp 10–999, saveSettings(), undoStack.shift() while over limit
rename listener (file, oldPath) => void Push undo; redoStack = []; FIFO trim
delete listener (file) => void Push undo; redoStack = []; FIFO trim
undoLastAction async undoLastAction() Pop undo; vault.rename or adapter mkdir/rename from .trash/<name>; push redo on success; Notices; finally ignore 1s
redoLastAction async redoLastAction() Pop redo; vault.rename or adapter rename to .trash/<name>; push undo on success; Notices; finally ignore 1s
dump-commands async () => void Node fs.writeFileSync vault-root commands_dump.txt; Notice "Commands dumped to commands_dump.txt"

Recipes

  1. Enable — Community plugins → File Undo (file-undo). Bind hotkeys in Settings → Hotkeys; addCommand registers none. History is empty until the next vault rename/delete in this session.
  2. Undo a move — Command palette "Undo last file move or deletion" (id: undo-last-file-action) → A1FileUndoPlugin.undoLastAction in main.js (rename branch). Notices use prefixes A1: / A1 Error:.
  3. Undo a deletion — Same command, delete branch: restores .trash/<file.name> to action.path via adapter (creates parent dirs if missing). Requires Obsidian Deleted files = vault .trash folder, not system/OS trash. Same-basename entries in .trash/ collide.
  4. Redo — "Redo last undone file move or deletion" (id: redo-last-file-action) → redoLastAction. Delete redo does not create .trash/ if missing.
  5. Cap history — Settings → A1 File Undo Settings → Max History Length (placeholder 200, min 10, max 999). display() onChange clamps, saveSettings(), trims undoStack only. Reload/disable clears both stacks.
  6. Dump command list — "A1 Debug: Dump all commands" (dump-commands) writes commands_dump.txt at the vault base path (Node fs / Electron; isDesktopOnly is false). Marked TEMPORARY DIAGNOSTIC COMMAND in source; still registered.
HealthExcellent
ReviewSatisfactory
About
Restore deleted files from Obsidian's .trash and revert moved or renamed files back to their original paths. Keep a configurable history of recent file operations and redo undone actions with dedicated commands.
FilesBackupCommands
Details
Current version
1.0.1
Last updated
2 months ago
Created
2 months ago
Updates
2 releases
Downloads
169
Compatible with
Obsidian 0.15.0+
Platforms
Desktop, Mobile
License
MIT
Report bugRequest featureReport plugin
Author
Jeff1024Jeff10242607044640
GitHub2607044640
  1. Community
  2. Plugins
  3. Files
  4. File Undo

Related plugins

Find orphaned files and broken links

Find files that are not linked anywhere and would otherwise be lost in your vault. In other words: files with no backlinks.

Print

Print notes and documents directly from your workspace.

Notebook Navigator

A better file browser and calendar inspired by Apple Notes, Bear, Evernote and Day One.

Claudian

Embeds Claude Code/Codex and other local Agents as AI collaborators in your vault.

Advanced URI

Control everything with URI.

Quick Switcher++

Enhanced Quick Switcher, search open panels, and symbols.

Thino

Quickly capture memos and display them in the sidebar with a heatmap. (Closed source)

Local REST API with MCP

Unlock your automation needs by interacting with your notes over a secure REST API.

ChatGPT MD

A seamless integration of ChatGPT, OpenRouter.ai and local LLMs via Ollama into your notes.

Copilot

Run AI agents such as Claude Code, Codex, and OpenCode inside your vault. Turn your second brain into a smart assistant that gets knowledge work done.