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.

SyntaxWrapperExtensions
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.