Reaper
unlistedby aioue
Silently deletes empty dated notes when closed, and completely empty folders after their last item is moved or deleted.
Reaper
Silently deletes empty dated notes when you close them, and completely empty folders after their last item is moved or deleted.
The intended use case for notes: you open a new note from a template, don't end up writing anything, and close it. The reaper cleans it up automatically without any confirmation prompts.
For folders: when the last note or file leaves a folder (moved or deleted), and the folder is completely empty - no files, notes, binaries, or hidden files - Reaper sends it to the system trash.
How it works
Empty notes
Obsidian does not emit a file-close event. Instead the plugin hooks layout-change and diffs the set of currently open markdown leaves against the previously tracked set. Any path that disappears from the open set is treated as a candidate for deletion.
For each closed path, the plugin reads the file content and runs it through a series of checks. Every check must pass - if any check fails the file is kept untouched.
Empty folders
- On vault
deleteandrenameevents, the parent folder of the affected path is queued (debounced ~200ms). - After an empty note is successfully reaped, its parent folder is also queued.
- Via the command Reap empty folders now, the plugin scans every folder already known to Obsidian's vault index (deepest-first) and reaps empties.
- Completeness check: Obsidian
TFolder.childrenmust be empty, and on desktopfs.readdirSyncon a path verified under the vault root must find no meaningful entries. Finder.DS_Storealone is ignored (treated as empty). Other hidden files (.git, AppleDouble._*, etc.) block reaping. On mobile (nofs), folders are never reaped. - Path resolution uses
realpathand rejects anything that escapes the vault base path. - Exclusions (folder prefixes, empty-folder denylist, template headings, root filenames) are matched case-insensitively. Excluded root filenames apply only in the vault root.
- After a folder is reaped, ancestors are walked upward if they become empty.
Deletion uses vault.trash(..., true) - system trash, not permanent delete - so there is always a safety net.
Note deletion criteria (all must be true)
| Check | Detail |
|---|---|
| Markdown file | .md extension only |
| Not in excluded folder | Configurable in settings (default: templates/, Excalidraw/, attachments/, .obsidian/) |
Not _-prefixed | Files starting with _ are kept |
| Not a known permanent root file | Configurable list of root filenames to never delete |
| Created today | ctime must be today's date - old notes you happen to close are never deleted |
| Content under 2 KB | Guards against a read failure returning an empty string |
| Empty body | After stripping YAML frontmatter, the remainder must contain only blank lines or template scaffold headings |
| File still exists at deletion time | Re-checked immediately before vault.trash() |
| File not re-opened | Re-checked after the async read |
Folder deletion criteria
| Check | Detail |
|---|---|
| Setting enabled | Reap empty folders is on (default) |
| Not vault root | Root is never reaped |
| Not excluded | Matches neither excluded folder prefixes nor extra excluded empty-folder paths |
| Completely empty | No Obsidian children and (on desktop) no filesystem entries including hidden files |
What "empty body" means
Template headings (configurable in settings, default: ## Notes, ## Links) are treated as empty content. A note containing only frontmatter and those headings with nothing written under them is considered empty.
Anything else in the body - a single word, a list item, a link - causes the file to be kept.
Settings
Open Settings → Reaper to configure:
- Excluded folder prefixes - folders whose notes are never deleted, and whose empty folders are never reaped
- Excluded root files - specific filenames in the vault root that are never deleted
- Template heading lines - headings injected by your note template that should be treated as "empty" content
- Reap empty folders - toggle empty-folder cleanup (default on)
- Excluded empty folders - extra folder paths that should never be reaped even if empty
Commands
- Reap empty folders now - scan Obsidian's vault folder index and trash completely empty folders (deepest-first)
Installation
This plugin is not on the community registry. Install manually:
-
Clone this repository:
git clone https://github.com/aioue/obsidian-plugin-reaper.git cd obsidian-plugin-reaper -
Copy the release bundle into your vault's plugin folder:
bash bundle.sh mkdir -p /path/to/your-vault/.obsidian/plugins/reaper cp release/main.js release/manifest.json /path/to/your-vault/.obsidian/plugins/reaper/Or run
bash deploy.sh/npm run deployif your vault path matches the script.For ongoing updates, prefer BRAT with repo
aioue/obsidian-plugin-reaper(tracks GitHub releases;updateAtStartup). -
In Obsidian: Settings → Community plugins → Enable → Reaper
Source stays split (main.js + safety.js) for tests. bundle.sh produces a single BRAT-compatible release/main.js.
Tests
npm test
tests/safety.test.mjs and tests/wiring.test.mjs cover note keep/reap rules, folder emptiness fail-safes (including .DS_Store-only = empty), secure path resolution, and a wiring harness for delete→parent-reap behaviour.
Migration from Empty Note Reaper
If you previously used empty-note-reaper:
- Disable the old plugin, then remove
.obsidian/plugins/empty-note-reaper/ - Install into
.obsidian/plugins/reaper/as above - Update
community-plugins.jsonto listreaperinstead ofempty-note-reaper - Copy
data.jsonfrom the old folder if you had custom settings
Files
| File | Purpose |
|---|---|
main.js | Plugin source (Obsidian wiring; requires ./safety.js in dev) |
safety.js | Pure keep/reap decision helpers (unit-tested) |
bundle.sh | esbuild → single release/main.js for BRAT/deploy |
manifest.json | Obsidian plugin manifest |
tests/ | Deletion-safety and wiring tests |
deploy.sh | Bundles and copies into Tom's main vault plugin folder |
README.md | This file |
Version
1.2.1 - No empty-folder scan on Obsidian load; GitHub Actions auto-release on main when manifest version is new.
1.2.0 - .DS_Store-only folders count as empty; case-insensitive exclusions; root-only excluded filenames; secure realpath vault checks; wiring tests; BRAT release bundle.
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.