Problem
contentManager.setProperty operates on a single file per call. When a vault-wide taxonomy change is needed — renaming a noteType, migrating a tag, or updating a status across dozens of files — the current workflow requires:
searchManager.searchContent to find matching files
- Assembling N individual
setProperty calls
- Executing them via
useTools (limited by call batch size)
- Handling partial failures manually
This is error-prone, token-expensive, and slow for LLM-driven vault maintenance workflows. A vault with 500+ notes that needs a frontmatter property renamed across 40 files currently requires ~45 tool calls (1 search + 40 setProperty + verification), consuming significant context window and round-trips.
Proposed solution
A new contentManager.batchSetProperty tool with two modes:
Mode 1: Explicit operations
Direct batch of {path, property, value} operations when the caller already knows which files to modify.
{
"operations": [
{ "path": "folder/note1.md", "property": "noteType", "value": "documentation", "mode": "replace" },
{ "path": "folder/note2.md", "property": "status", "value": "archived", "mode": "replace" }
],
"dryRun": false
}
Mode 2: Search-and-set (primary value)
Combines search + mutation in a single atomic operation. This is the high-value mode — it eliminates the intermediate search step entirely.
{
"filter": { "property": "noteType", "value": "old-type" },
"set": { "property": "noteType", "value": "new-type" },
"paths": ["Production/"],
"dryRun": true
}
Parameters
| Parameter |
Type |
Required |
Description |
operations |
Array<{path, property, value, mode?}> |
Mode 1 |
Explicit list of operations |
filter |
{property, value} |
Mode 2 |
Match condition: files where property === value |
set |
{property, value, mode?} |
Mode 2 |
What to set on matched files |
paths |
string[] |
No |
Scope restriction (path prefix match). Applies to both modes as safety guard. |
dryRun |
boolean |
No (default: false) |
Preview without modifying. Returns list of files that would be affected. |
mode follows the same semantics as setProperty: "replace" (default) or "merge" (array union with dedup).
Response
dryRun response:
{
"success": true,
"dryRun": true,
"wouldAffect": [
{ "path": "folder/note1.md", "currentValue": "old-type" },
{ "path": "folder/note2.md", "currentValue": "old-type" }
],
"count": 2
}
Execution response:
{
"success": true,
"affected": [
{ "path": "folder/note1.md", "previousValue": "old-type", "newValue": "new-type" },
{ "path": "folder/note2.md", "previousValue": "old-type", "newValue": "new-type" }
],
"errors": [],
"count": 2
}
Safety constraints
- Max operations per call: 200 (configurable). Prevents accidental vault-wide damage.
- Best-effort execution: A failure on one file does not abort the batch. Errors are collected and returned alongside successes.
- dryRun as first-class citizen: LLM agents can preview impact before committing. This is critical for trust in automated workflows.
- Path scoping:
paths parameter restricts the blast radius. In Mode 2, files outside the scope are never touched even if they match the filter.
Use cases
1. Taxonomy maintenance
Renaming a noteType across a vault section when consolidating redundant types:
{ "filter": {"property": "noteType", "value": "technical-doc"}, "set": {"property": "noteType", "value": "documentation"}, "paths": ["Production/"], "dryRun": true }
2. Bulk status transitions
Archiving all completed initiatives:
{ "filter": {"property": "status", "value": "completed"}, "set": {"property": "status", "value": "archived"}, "paths": ["Production/"] }
3. Tag migration
Renaming a tag across the vault:
{ "filter": {"property": "tags", "value": "old-tag"}, "set": {"property": "tags", "value": "new-tag"}, "mode": "replace" }
4. Vault hygiene pipelines
LLM-driven audit workflows that detect non-conforming frontmatter and fix it in batch, with human review via dryRun.
Implementation notes
- The core logic reuses
app.fileManager.processFrontMatter() (same as setProperty)
- Mode 2 filtering can use
app.vault.getMarkdownFiles() + app.metadataCache.getFileCache(file)?.frontmatter for efficient matching without full-text search
- Path scoping is a prefix match on
file.path — simple string operation
- Tool registration follows the same pattern as existing contentManager tools
Alternatives considered
| Alternative |
Why not sufficient |
Multiple setProperty calls via useTools parallel |
Works but token-expensive (N tool schemas in context), no dryRun, no atomic search+set |
| External script manipulating .md files |
Bypasses Obsidian’s file manager, misses metadata cache updates, risks sync conflicts |
| Dataview/Templater batch |
Not available via MCP, requires user interaction |
Relationship to existing tools
setProperty: single-file operation. batchSetProperty is the multi-file counterpart.
searchContent: read-only search. batchSetProperty Mode 2 combines search with mutation.
replace: content-level editing. batchSetProperty is frontmatter-level only (structured, safer).
Submitted by ThinkBox project. We maintain a monkey-patch implementing this feature and are happy to contribute a PR if the design is accepted.
Problem
contentManager.setPropertyoperates on a single file per call. When a vault-wide taxonomy change is needed — renaming anoteType, migrating a tag, or updating a status across dozens of files — the current workflow requires:searchManager.searchContentto find matching filessetPropertycallsuseTools(limited by call batch size)This is error-prone, token-expensive, and slow for LLM-driven vault maintenance workflows. A vault with 500+ notes that needs a frontmatter property renamed across 40 files currently requires ~45 tool calls (1 search + 40 setProperty + verification), consuming significant context window and round-trips.
Proposed solution
A new
contentManager.batchSetPropertytool with two modes:Mode 1: Explicit operations
Direct batch of
{path, property, value}operations when the caller already knows which files to modify.{ "operations": [ { "path": "folder/note1.md", "property": "noteType", "value": "documentation", "mode": "replace" }, { "path": "folder/note2.md", "property": "status", "value": "archived", "mode": "replace" } ], "dryRun": false }Mode 2: Search-and-set (primary value)
Combines search + mutation in a single atomic operation. This is the high-value mode — it eliminates the intermediate search step entirely.
{ "filter": { "property": "noteType", "value": "old-type" }, "set": { "property": "noteType", "value": "new-type" }, "paths": ["Production/"], "dryRun": true }Parameters
operationsArray<{path, property, value, mode?}>filter{property, value}property === valueset{property, value, mode?}pathsstring[]dryRunbooleanfalse)modefollows the same semantics assetProperty:"replace"(default) or"merge"(array union with dedup).Response
dryRun response:
{ "success": true, "dryRun": true, "wouldAffect": [ { "path": "folder/note1.md", "currentValue": "old-type" }, { "path": "folder/note2.md", "currentValue": "old-type" } ], "count": 2 }Execution response:
{ "success": true, "affected": [ { "path": "folder/note1.md", "previousValue": "old-type", "newValue": "new-type" }, { "path": "folder/note2.md", "previousValue": "old-type", "newValue": "new-type" } ], "errors": [], "count": 2 }Safety constraints
pathsparameter restricts the blast radius. In Mode 2, files outside the scope are never touched even if they match the filter.Use cases
1. Taxonomy maintenance
Renaming a
noteTypeacross a vault section when consolidating redundant types:{ "filter": {"property": "noteType", "value": "technical-doc"}, "set": {"property": "noteType", "value": "documentation"}, "paths": ["Production/"], "dryRun": true }2. Bulk status transitions
Archiving all completed initiatives:
{ "filter": {"property": "status", "value": "completed"}, "set": {"property": "status", "value": "archived"}, "paths": ["Production/"] }3. Tag migration
Renaming a tag across the vault:
{ "filter": {"property": "tags", "value": "old-tag"}, "set": {"property": "tags", "value": "new-tag"}, "mode": "replace" }4. Vault hygiene pipelines
LLM-driven audit workflows that detect non-conforming frontmatter and fix it in batch, with human review via dryRun.
Implementation notes
app.fileManager.processFrontMatter()(same assetProperty)app.vault.getMarkdownFiles()+app.metadataCache.getFileCache(file)?.frontmatterfor efficient matching without full-text searchfile.path— simple string operationAlternatives considered
setPropertycalls viauseToolsparallelRelationship to existing tools
setProperty: single-file operation.batchSetPropertyis the multi-file counterpart.searchContent: read-only search.batchSetPropertyMode 2 combines search with mutation.replace: content-level editing.batchSetPropertyis frontmatter-level only (structured, safer).Submitted by ThinkBox project. We maintain a monkey-patch implementing this feature and are happy to contribute a PR if the design is accepted.