CLI
Working with Frontmatter
Read, create, query, and edit YAML frontmatter in Markdown and other text files.
Use md-utils fm to read, create, query, and edit YAML frontmatter. The same
commands work with Markdown and supported non-Markdown text files. A
non-Markdown file has a few extra syntax and selection rules so its metadata
does not break the host language.
Markdown frontmatter
In a Markdown file, frontmatter is not wrapped in a comment. Put the YAML at
the top of the file with --- on the lines immediately before and after it:
---
title: Example
status: draft
---
# Example
Non-Markdown frontmatter
In a supported non-Markdown source file, the complete YAML frontmatter block must be wrapped in the comment or string syntax mapped to the file extension. Unlike Markdown frontmatter, the wrapped block does not have to be at the top of the file. It may appear anywhere, although placing it at or near the beginning is recommended for discoverability and scan efficiency.
JavaScript, Swift, and other C-like languages use a block comment:
/*
---
title: Example
status: draft
---
*/
export function example() {}
HTML and related formats use an HTML comment:
<!--
---
title: Example
status: draft
---
-->
<main>Example</main>
Python uses a triple-quoted string:
"""
---
title: Example
status: draft
---
"""
def example():
pass
For md-utils to recognize wrapped frontmatter:
- The file extension must have a shipped syntax mapping.
- The opening wrapper, both
---delimiters, and the closing wrapper must each occupy a complete line. - The content between the
---delimiters must be a YAML mapping. - The opening and closing wrappers must belong to the same mapped syntax.
- The file must use LF line endings. Pure CRLF input is not currently supported.
Ideally, a non-Markdown file contains exactly one complete frontmatter block.
md-utils parses the first complete block. If it finds another complete block,
it ignores that later block’s YAML and emits an error identifying its opening
line. Read-only commands may still expose the first block according to their
command contract. Mutating commands refuse to change a file with multiple
complete blocks, leaving it byte-for-byte unchanged. An incomplete block is
treated as absent.
Supported file types
Mappings are selected case-insensitively from the extension. They are built
into md-utils; --extensions can narrow a selection, but it cannot add a
mapping.
| Syntax | Wrapper | Extensions |
|---|---|---|
| C-style block | /* and */ | c, h, cc, cpp, cxx, hpp, hxx, m, mm, swift, java, kt, kts, scala, js, mjs, cjs, jsx, ts, mts, cts, tsx, cs, go, rs, dart, php, css, scss, less, sql, jsonc |
| HTML comment | <!-- and --> | html, htm, xhtml, xml, svg, vue, svelte |
| Python docstring | """ and """ | py, pyi |
| PowerShell block comment | <# and #> | ps1, psm1, psd1 |
| Lua block comment | --[[ and ]] | lua |
| Markdown-style text | --- and --- | txt |
Formats that require a prefix on every metadata line, such as shell scripts,
Ruby, YAML, TOML, and R, do not have mappings. Ordinary JSON is unsupported
because JSON has no comment syntax; JSONC uses the C-style block mapping.
Per-line comments and custom wrapper mappings are not currently supported.
Plain .txt is a deliberate exception to the wrapper rule: with
--include-non-md, it uses ordinary unwrapped Markdown-style frontmatter.
Select files
One explicitly named mapped file is enough to opt in:
md-utils fm get --key title Sources/Example.swift
When you name multiple files or a directory, non-Markdown files are excluded
by default. Add --include-non-md to include every mapped file in the
selection:
md-utils fm set --key reviewed --value true --include-non-md Sources/
An explicitly listed non-Markdown file in a multi-file command is left
unchanged without that flag, and md-utils prints an opt-in hint. Files found
while traversing a directory are skipped silently unless you opt in.
Plain .txt is the exception to automatic single-file selection. It uses
ordinary Markdown-style frontmatter and always requires --include-non-md:
md-utils fm get --key title --include-non-md notes.txt
An explicitly named file with an unmapped extension produces an error and is left unchanged.
Read frontmatter
All frontmatter read operations understand mapped wrappers. Read one value:
md-utils fm get --key title Sources/Example.swift
Inspect the complete mapping as YAML:
md-utils fm dump --format yaml Sources/Example.swift
You can also use fm has to test for a key, fm list to list keys, fm unique
to check value uniqueness, and fm search to select files with a JMESPath
expression.
Create frontmatter
Creating a wrapper changes the host source, so md-utils requires explicit
authorization when a non-Markdown file has no frontmatter.
For one explicit file in an interactive terminal, a mutating command asks for confirmation:
$ md-utils fm set --key status --value draft Sources/Example.swift
Create wrapped frontmatter using c-block (/* … */)? [y/N]
Answering y inserts the mapped block at line 1, followed by one blank line
and the original content. Any other answer leaves the file unchanged.
Use --create-frontmatter to authorize creation without a prompt:
md-utils fm set --key status --value draft \
--create-frontmatter Sources/Example.swift
Batch operations never prompt. When processing multiple files or a directory, use both selection and creation flags if mapped files might not already have a block:
md-utils fm set --key status --value draft \
--include-non-md --create-frontmatter Sources/
Without --create-frontmatter, files that need a new wrapper are reported and
left unchanged while the command continues with the rest of the selection.
Markdown files retain their existing behavior: mutating commands create
ordinary frontmatter automatically.
Edit frontmatter
Once a file contains one valid block, mutation works the same way as it does for Markdown. Set or update a value:
md-utils fm set --key status --value published Sources/Example.swift
Remove a key:
md-utils fm remove --key draft Sources/Example.swift
Rename a key:
md-utils fm rename --key owner --new-key maintainer Sources/Example.swift
The fm replace, fm sort-keys, fm touch, fm remove-frontmatter, and
fm array commands also support mapped non-Markdown files. Edits preserve the
mapped wrapper, surrounding source, and line endings.
Before a mutation, md-utils rereads the file and checks that it has not
changed since parsing. It writes with atomic replacement. If the source
revision changed, the command fails instead of silently applying stale edit
coordinates. There is still a small race window if another uncoordinated
process writes between the final check and replacement.
Validate with rules
Wrapped frontmatter also participates in md-utils rules. A rule path that
selects a non-Markdown extension, such as Sources/**/*.swift, opts those
files into project scanning. Frontmatter field predicates, JMESPath
predicates, $md-utils type hints, and JSON Schema validation then use the
unwrapped YAML just as they do for Markdown records.
Text predicates see the host source with the wrapper removed. Markdown-only structure such as headings and sections is not inferred from source code; predicates that require that structure produce an unsupported diagnostic. File-only predicates can still evaluate an unmapped file. Rules inspect and validate records but do not create or edit frontmatter.
Invalid and missing blocks
md-utils handles ambiguous input conservatively:
- An incomplete wrapper or delimiter sequence counts as no frontmatter.
- Malformed YAML produces a parsing diagnostic.
- The first complete wrapped block is used; subsequent complete blocks are ignored and produce errors identifying their opening lines.
- Mutating a file with multiple complete blocks is refused, leaving the file byte-for-byte unchanged.
- A wrapper that does not match the extension’s shipped mapping is not treated as frontmatter.
If a command does not behave as expected, start with md-utils fm dump FILE to
check whether the block is recognized, then compare its physical lines and
wrapper against the rules above.