obsidian_batch_rename
Batch rename or move notes in an Obsidian vault, with automatic link updates. Perform a dry run to preview changes before applying.
Instructions
Batch move/rename notes with optional incoming link updates. Dry-run by default.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| vault | No | Optional configured vault name. Defaults to the server default vault. | |
| moves | Yes | ||
| dryRun | No | ||
| overwrite | No | ||
| updateLinks | No |
Implementation Reference
- src/tools.ts:597-608 (registration)Registration of the 'obsidian_batch_rename' tool with its schema and handler, which delegates to the batchRename function from ops.ts.
tool( "obsidian_batch_rename", "Batch move/rename notes with optional incoming link updates. Dry-run by default.", { vault: vaultArg, moves: z.array(z.object({ from: z.string(), to: z.string() })).min(1).max(500), dryRun: z.boolean().optional().default(true), overwrite: z.boolean().optional().default(false), updateLinks: z.boolean().optional().default(true), }, async (args) => batchRename(vaults, args.vault, args.moves, args), ); - src/tools.ts:600-606 (schema)Zod schema for obsidian_batch_rename: vault (optional), moves (array of {from, to}, min 1 max 500), dryRun (default true), overwrite (default false), updateLinks (default true).
{ vault: vaultArg, moves: z.array(z.object({ from: z.string(), to: z.string() })).min(1).max(500), dryRun: z.boolean().optional().default(true), overwrite: z.boolean().optional().default(false), updateLinks: z.boolean().optional().default(true), }, - src/ops.ts:93-122 (handler)The actual batchRename handler function. Iterates over moves, calls vaults.move() for each, optionally updates links via updateLinksAcrossVault, and supports dry-run mode.
export async function batchRename( vaults: VaultManager, vault: string | undefined, moves: Array<{ from: string; to: string }>, options: { dryRun?: boolean; overwrite?: boolean; updateLinks?: boolean } = {}, ): Promise<{ dryRun: boolean; moves: Array<{ from: string; to: string; ok: boolean; error?: string }>; rewrites: Array<{ path: string; changed: number; preview?: string }> }> { const results = []; const rewrites = []; for (const move of moves) { try { if (!options.dryRun) { const result = await vaults.move(vaults.notePath(move.from), vaults.notePath(move.to), vault, { overwrite: options.overwrite }); results.push({ ...result, ok: true }); if (options.updateLinks !== false) { rewrites.push(...await updateLinksAcrossVault(vaults, vault, result.from, result.to, { dryRun: false })); } } else { vaults.resolvePath(vaults.notePath(move.from), vault); vaults.resolvePath(vaults.notePath(move.to), vault); results.push({ from: vaults.notePath(move.from), to: vaults.notePath(move.to), ok: true }); if (options.updateLinks !== false) { rewrites.push(...await updateLinksAcrossVault(vaults, vault, vaults.notePath(move.from), vaults.notePath(move.to), { dryRun: true })); } } } catch (error) { results.push({ from: move.from, to: move.to, ok: false, error: error instanceof Error ? error.message : String(error) }); } } return { dryRun: options.dryRun ?? true, moves: results, rewrites }; } - src/ops.ts:55-91 (helper)updateLinksAcrossVault is the helper used by batchRename to rewrite wiki-links and markdown links referencing the old path to the new path across all markdown files.
export async function updateLinksAcrossVault( vaults: VaultManager, vault: string | undefined, fromPath: string, toPath: string, options: { dryRun?: boolean } = {}, ): Promise<Array<{ path: string; changed: number; preview?: string }>> { const files = await vaults.markdownFiles(vault); const fromNoExt = fromPath.replace(/\.md$/i, ""); const fromBase = path.posix.basename(fromNoExt); const toNoExt = toPath.replace(/\.md$/i, ""); const rewrites: Array<{ path: string; changed: number; preview?: string }> = []; for (const file of files) { const read = await vaults.readText(file, vault); let changed = 0; let next = read.text.replace(/(!?)\[\[([^\]\n]+)\]\]/g, (full, embed: string, inner: string) => { const [targetAndSection, display] = inner.split("|", 2); const sectionMatch = /([#^].*)$/.exec(targetAndSection); const section = sectionMatch?.[1] ?? ""; const target = targetAndSection.replace(/[#^].*$/, "").trim().replace(/\.md$/i, ""); if (target !== fromNoExt && target !== fromBase && target !== fromPath) return full; changed += 1; return `${embed}[[${toNoExt}${section}${display ? `|${display}` : ""}]]`; }); next = next.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, (full, label: string, target: string) => { const clean = decodeURIComponent(target).replace(/^\.\//, ""); if (clean !== fromPath && clean !== fromNoExt && clean !== `${fromNoExt}.md`) return full; changed += 1; return `[${label}](${encodeURI(toPath)})`; }); if (changed > 0) { rewrites.push({ path: read.path, changed, preview: options.dryRun ? previewDiff(read.text, next) : undefined }); if (!options.dryRun) await vaults.writeText(read.path, next, vault, { overwrite: true }); } } return rewrites; } - src/tools.ts:28-28 (helper)Import of batchRename from ops.ts into tools.ts.
import { batchRename, deleteFolder, pruneEmptyDirs, regexReplaceAcrossVault, updateLinksAcrossVault } from "./ops.js";