markdown-patch - v1.0.0

markdown-patch

Make targeted, structure-aware edits to Markdown documents — without sed.

Instead of treating a document as a blob of text, markdown-patch understands its structure (headings, block references, frontmatter) and lets you append, prepend, or replace content at a specific location within it.

Available as both a CLI tool (mdpatch) and a TypeScript/JavaScript library.

API docs: https://coddingtonbear.github.io/markdown-patch/

npm install markdown-patch

The mdpatch binary is included and available after install.

Given a document notes.md:

---
status: in-progress
---

# Meeting Notes

## Action Items

- Follow up with design team

Append a new item under Action Items:

echo "- Send the report" | mdpatch patch append heading "Meeting Notes::Action Items" notes.md

Replace the status frontmatter field:

echo '"done"' | mdpatch patch replace frontmatter status notes.md

Not sure what targets exist in a document? Use print-map:

mdpatch print-map notes.md

Apply a single patch operation.

mdpatch patch [options] <operation> <targetType> <target> <documentPath>
  • <operation>append, prepend, or replace
  • <targetType>heading, block, or frontmatter
  • <target> — the target address (see below)
  • <documentPath> — file to modify (patched in-place by default)

Options:

Flag Description
-i, --input <path> Read content from a file instead of stdin
-o, --output <path> Write result to a file instead of patching in-place; use - for stdout
-d, --delimiter <str> Heading path delimiter (default: ::)

Apply one or more patch instructions from a JSON patch file.

mdpatch apply [options] <documentPath> <patchFile>

The patch file should be a JSON object (single instruction) or JSON array (multiple instructions). Use - to read from stdin.

Extract the content of a specific target and write it to stdout (or a file).

mdpatch query [options] <targetType> <target> <documentPath>

Show all patchable targets discovered in a document, useful for finding the right target address.

mdpatch print-map <documentPath> [regex]

Address a section by its heading path, delimited by :: (or a custom delimiter). Nested headings use the full path:

# Target the top-level "Overview" section
mdpatch patch append heading "Overview" notes.md

# Target a nested heading
mdpatch patch append heading "Meeting Notes::Action Items" notes.md

Address a paragraph, table, or other block by its Obsidian block ID (e.g. ^abc123):

echo "New row content" | mdpatch patch append block "abc123" notes.md

When the target block is a Markdown table and content type is application/json, rows can be appended or prepended as JSON arrays.

Address a YAML frontmatter key by name. Content is treated as JSON:

# Set a scalar
echo '"done"' | mdpatch patch replace frontmatter status notes.md

# Append to a list
echo '"new-tag"' | mdpatch patch append frontmatter tags notes.md
import { applyPatch, getDocumentMap } from "markdown-patch";

const document = `# My Note\n\n## Tasks\n\n- Buy milk\n`;

const patched = applyPatch(document, {
operation: "append",
targetType: "heading",
target: ["My Note", "Tasks"],
content: "- Write tests\n",
});

getDocumentMap parses a document and returns its structure — useful for inspecting what headings, blocks, and frontmatter fields are available before patching.

Option Type Description
operation "append" | "prepend" | "replace" What to do
targetType "heading" | "block" | "frontmatter" What to target
target string | string[] Target address (array for heading paths)
content string Content to apply
contentType "text/markdown" | "application/json" Defaults to text/markdown
targetScope "content" | "marker" | "markerAndContent" Heading and block only. Defaults to "content". "marker" targets only the heading line or block ID (useful for renaming). "markerAndContent" targets the full range covering both.
createTargetIfMissing boolean Create the heading or block if it doesn't exist
rejectIfContentPreexists boolean Reject the patch (with ContentAlreadyPreexistsInTarget) if the supplied content is already present in the target. Ignored for replace operations.
trimTargetWhitespace boolean Trim whitespace from the target boundary before joining