Markdown documents commonly include YAML frontmatter — a block of metadata delimited by --- at the top of the file. Tools like Jekyll, Hugo, MkDocs, and GitHub Pages all rely on frontmatter for page titles, tags, dates, layout settings, and other metadata. When automating documentation pipelines or generating static-site content from PowerShell, there is currently no way to read or write frontmatter through the Markdown module.
Request
Desired capability
When ConvertFrom-Markdown parses a markdown string that begins with YAML frontmatter (--- delimiters), the resulting MarkdownDocument object should populate its FrontMatter property with the parsed metadata. When ConvertTo-Markdown serializes a MarkdownDocument back to a string, it should re-emit that property as a valid YAML frontmatter block at the top of the document.
The MarkdownFrontMatter type and the MarkdownDocument.FrontMatter property are defined by #8 in 1.3 but left permanently $null there. This issue fills them in, targeting 1.4.
Because the property is reserved up front, this is a minor bump rather than a major one: ConvertFrom-Markdown already returns a MarkdownDocument, the property already exists and is already typed, and no index in Children shifts. The only observable change is that a property which was always $null is now sometimes populated.
This enables round-tripping documents that contain frontmatter without losing or corrupting the metadata.
Example input:
---
title: Getting Started
date: 2026-01-15
tags:
- powershell
- markdown
layout: docs
---
# Getting Started
This is the introduction.
Expected behavior with ConvertFrom-Markdown:
$doc = Get-Content -Raw 'article.md' | ConvertFrom-Markdown
$doc.FrontMatter.Data
# Returns: @{ title = 'Getting Started'; date = '2026-01-15'; tags = @('powershell','markdown'); layout = 'docs' }
$doc.FrontMatter.Format
# Returns: Yaml
$doc.FrontMatter.Raw
# Returns: the verbatim text between the delimiters
$doc.Children
# Returns: the parsed markdown block nodes (headings, paragraphs, etc.) — without the frontmatter block
Expected behavior with ConvertTo-Markdown:
$doc.FrontMatter.Data['draft'] = $true
$doc | ConvertTo-Markdown
# Returns the markdown string with updated YAML frontmatter at the top, followed by the document content
Acceptance criteria
ConvertFrom-Markdown detects YAML frontmatter (delimited by --- on the first line and a closing ---) and populates MarkdownDocument.FrontMatter with a MarkdownFrontMatter instance
MarkdownFrontMatter.Data holds the deserialized metadata, MarkdownFrontMatter.Raw holds the verbatim text between the delimiters, and MarkdownFrontMatter.Format is Yaml
- The frontmatter block is excluded from the parsed content nodes — it is metadata, not document content
ConvertTo-Markdown re-emits the frontmatter block at the top of the output string when FrontMatter is non-null
- Documents without frontmatter produce a
$null FrontMatter property and round-trip without adding spurious --- delimiters
- YAML parsing and serialization is delegated to the PSModule/YAML module (
ConvertFrom-Yaml / ConvertTo-Yaml)
- Nested YAML structures (arrays, nested objects) are preserved through round-tripping
Dependencies
This feature depends on:
- PSModule/Markdown#8 — 1.3 delivers the object hierarchy,
ConvertFrom-Markdown, and ConvertTo-Markdown, and defines the MarkdownFrontMatter type this issue populates
- PSModule/YAML#3 —
ConvertFrom-Yaml for parsing the frontmatter block
- PSModule/YAML#2 —
ConvertTo-Yaml for serializing the metadata back to YAML
Technical decisions
Property name and type: [MarkdownFrontMatter] $FrontMatter on MarkdownDocument, as defined in #8. This supersedes the earlier decision to use a bare [hashtable] $Metadata. A dedicated type carries three things a hashtable cannot: Format (which metadata dialect the block uses), Raw (the verbatim source text, so a document whose metadata is never touched round-trips losslessly), and Data (the deserialized value). It also leaves room for TOML (+++) and JSON frontmatter without another breaking property change.
Data type: [hashtable] for MarkdownFrontMatter.Data. This matches the output of ConvertFrom-Yaml -AsHashtable and is the most natural shape for key-value metadata that scripts modify programmatically. A [PSCustomObject] alternative was considered and rejected on ergonomics.
Frontmatter is a property, never a child node: mdast models frontmatter as the first child of the root. That approach is rejected here because it shifts every index in Children for documents that have frontmatter, and forces every traversal to skip a node that is not markdown. As a property it is invisible to anything walking the tree, which is what keeps this release additive.
Emission uses Raw when unchanged: When Data has not been modified, ConvertTo-Markdown re-emits Raw verbatim so key order, comments, and formatting survive. When Data has been modified, the block is re-serialized with ConvertTo-Yaml and Raw is refreshed.
Release shape: A minor bump — 1.4. Nothing changes shape; a reserved property starts being populated.
YAML dependency: Frontmatter parsing and serialization is delegated entirely to the PSModule/YAML module. The Markdown module declares a module dependency on YAML. This avoids reimplementing YAML parsing and ensures consistency with the rest of the PSModule ecosystem.
Frontmatter detection: The parser detects frontmatter only when the document starts with --- on the very first line (optionally preceded by a UTF-8 BOM). The closing --- delimiter ends the frontmatter block. Content before the first --- or documents that do not start with --- are treated as having no frontmatter. This matches the Jekyll frontmatter specification.
Separation of concerns: Frontmatter extraction happens as a preprocessing step in ConvertFrom-Markdown before the block parser runs. The raw text between the delimiters is passed to ConvertFrom-Yaml, and the remainder of the document (after the closing ---) is passed to the block parser. This keeps the frontmatter logic isolated from the structural parser.
ConvertTo-Markdown emission: When FrontMatter is non-null, ConvertTo-Markdown renders the block, wraps it in --- delimiters, and prepends it to the rendered document content. When FrontMatter is null, no frontmatter block is emitted.
File placement: The frontmatter logic is added to the existing ConvertFrom-Markdown.ps1 and ConvertTo-Markdown.ps1 functions and their private helpers (from #8). The MarkdownFrontMatter class already exists from #8 and only gains behavior here. No separate functions are needed — frontmatter is an integral part of document parsing, not a standalone operation.
Test approach: Pester tests in the existing tests/Markdown.Tests.ps1. Separate Context blocks under the existing Describe blocks for ConvertFrom-Markdown and ConvertTo-Markdown, plus a frontmatter-specific round-trip test.
Implementation plan
Class changes
ConvertFrom-Markdown changes
ConvertTo-Markdown changes
Module dependency
Tests
Documentation
Markdown documents commonly include YAML frontmatter — a block of metadata delimited by
---at the top of the file. Tools like Jekyll, Hugo, MkDocs, and GitHub Pages all rely on frontmatter for page titles, tags, dates, layout settings, and other metadata. When automating documentation pipelines or generating static-site content from PowerShell, there is currently no way to read or write frontmatter through the Markdown module.Request
Desired capability
When
ConvertFrom-Markdownparses a markdown string that begins with YAML frontmatter (---delimiters), the resultingMarkdownDocumentobject should populate itsFrontMatterproperty with the parsed metadata. WhenConvertTo-Markdownserializes aMarkdownDocumentback to a string, it should re-emit that property as a valid YAML frontmatter block at the top of the document.The
MarkdownFrontMattertype and theMarkdownDocument.FrontMatterproperty are defined by #8 in 1.3 but left permanently$nullthere. This issue fills them in, targeting 1.4.Because the property is reserved up front, this is a minor bump rather than a major one:
ConvertFrom-Markdownalready returns aMarkdownDocument, the property already exists and is already typed, and no index inChildrenshifts. The only observable change is that a property which was always$nullis now sometimes populated.This enables round-tripping documents that contain frontmatter without losing or corrupting the metadata.
Example input:
Expected behavior with
ConvertFrom-Markdown:Expected behavior with
ConvertTo-Markdown:Acceptance criteria
ConvertFrom-Markdowndetects YAML frontmatter (delimited by---on the first line and a closing---) and populatesMarkdownDocument.FrontMatterwith aMarkdownFrontMatterinstanceMarkdownFrontMatter.Dataholds the deserialized metadata,MarkdownFrontMatter.Rawholds the verbatim text between the delimiters, andMarkdownFrontMatter.FormatisYamlConvertTo-Markdownre-emits the frontmatter block at the top of the output string whenFrontMatteris non-null$nullFrontMatterproperty and round-trip without adding spurious---delimitersConvertFrom-Yaml/ConvertTo-Yaml)Dependencies
This feature depends on:
ConvertFrom-Markdown, andConvertTo-Markdown, and defines theMarkdownFrontMattertype this issue populatesConvertFrom-Yamlfor parsing the frontmatter blockConvertTo-Yamlfor serializing the metadata back to YAMLTechnical decisions
Property name and type:
[MarkdownFrontMatter] $FrontMatteronMarkdownDocument, as defined in #8. This supersedes the earlier decision to use a bare[hashtable] $Metadata. A dedicated type carries three things a hashtable cannot:Format(which metadata dialect the block uses),Raw(the verbatim source text, so a document whose metadata is never touched round-trips losslessly), andData(the deserialized value). It also leaves room for TOML (+++) and JSON frontmatter without another breaking property change.Data type:
[hashtable]forMarkdownFrontMatter.Data. This matches the output ofConvertFrom-Yaml -AsHashtableand is the most natural shape for key-value metadata that scripts modify programmatically. A[PSCustomObject]alternative was considered and rejected on ergonomics.Frontmatter is a property, never a child node: mdast models frontmatter as the first child of the root. That approach is rejected here because it shifts every index in
Childrenfor documents that have frontmatter, and forces every traversal to skip a node that is not markdown. As a property it is invisible to anything walking the tree, which is what keeps this release additive.Emission uses
Rawwhen unchanged: WhenDatahas not been modified,ConvertTo-Markdownre-emitsRawverbatim so key order, comments, and formatting survive. WhenDatahas been modified, the block is re-serialized withConvertTo-YamlandRawis refreshed.Release shape: A minor bump — 1.4. Nothing changes shape; a reserved property starts being populated.
YAML dependency: Frontmatter parsing and serialization is delegated entirely to the PSModule/YAML module. The Markdown module declares a module dependency on YAML. This avoids reimplementing YAML parsing and ensures consistency with the rest of the PSModule ecosystem.
Frontmatter detection: The parser detects frontmatter only when the document starts with
---on the very first line (optionally preceded by a UTF-8 BOM). The closing---delimiter ends the frontmatter block. Content before the first---or documents that do not start with---are treated as having no frontmatter. This matches the Jekyll frontmatter specification.Separation of concerns: Frontmatter extraction happens as a preprocessing step in
ConvertFrom-Markdownbefore the block parser runs. The raw text between the delimiters is passed toConvertFrom-Yaml, and the remainder of the document (after the closing---) is passed to the block parser. This keeps the frontmatter logic isolated from the structural parser.ConvertTo-Markdown emission: When
FrontMatteris non-null,ConvertTo-Markdownrenders the block, wraps it in---delimiters, and prepends it to the rendered document content. WhenFrontMatteris null, no frontmatter block is emitted.File placement: The frontmatter logic is added to the existing
ConvertFrom-Markdown.ps1andConvertTo-Markdown.ps1functions and their private helpers (from #8). TheMarkdownFrontMatterclass already exists from #8 and only gains behavior here. No separate functions are needed — frontmatter is an integral part of document parsing, not a standalone operation.Test approach: Pester tests in the existing
tests/Markdown.Tests.ps1. SeparateContextblocks under the existingDescribeblocks forConvertFrom-MarkdownandConvertTo-Markdown, plus a frontmatter-specific round-trip test.Implementation plan
Class changes
MarkdownFrontMatter.ToString()to render the frontmatter block including its---delimitersDatamodifications invalidateRawConvertFrom-Markdown changes
ConvertFrom-Markdown— check if input begins with------delimiters and store it asRawConvertFrom-Yaml -AsHashtableto produceDataMarkdownFrontMatterto$document.FrontMatter---to the block parser---\n---), frontmatter with only whitespaceConvertTo-Markdown changes
$document.FrontMatteris non-null at the start ofConvertTo-MarkdownRawverbatim whenDatais unmodified; otherwise callConvertTo-YamlonData---, the serialized output, and a closing---followed by a blank line before the document contentFrontMatteris null, emit no frontmatter blockModule dependency
YAMLas a required module dependency in the module manifest or loaderTests
Context 'Frontmatter'underDescribe 'ConvertFrom-Markdown'— test parsing of simple key-value, nested objects, arrays, and date valuesContext 'Frontmatter'underDescribe 'ConvertTo-Markdown'— test emission of the frontmatter block, and no emission whenFrontMatteris nullContext 'Frontmatter round-trip'— verifyConvertFrom-Markdown | ConvertTo-Markdownpreserves metadata through a full cycleRawverbatim, preserving key order and commentsFrontMatterand no---on re-emission---\n---) produces emptyDataDocumentation
ConvertFrom-MarkdownhelpConvertTo-MarkdownhelpREADME.mdwith a frontmatter example