Skip to content

# Feature Request: contentManager.batchSetProperty — Batch frontmatter operations with search-and-set #91

@gcp007-ops

Description

@gcp007-ops

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:

  1. searchManager.searchContent to find matching files
  2. Assembling N individual setProperty calls
  3. Executing them via useTools (limited by call batch size)
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions