Back to Blog
Developer Tools 2026-06-29 5 min

Markdown Frontmatter — What It Is and How to Use It

Author

If you've opened a .md file from a blog or documentation site, you've probably seen a block of text between triple dashes at the very top. That block is frontmatter. It's YAML, and it holds metadata about the file: the title, the publish date, tags, whether the post is a draft. Static site generators read that metadata and use it to build pages, sort posts, and generate HTML head elements. Most markdown files you encounter in real projects have frontmatter.

What Is Frontmatter?

Frontmatter is the block between the opening --- and the closing --- at the top of a .md or .mdx file. Everything inside that block is YAML. Everything after the closing delimiter is the markdown body.

---
title: "My First Post"
date: "2026-01-15"
author: "Jane Doe"
tags: ["markdown", "blog"]
published: true
---

This is the post body.

The parser reads both parts separately. The frontmatter becomes a data object your templates and page components can access. The body stays as markdown content to be rendered. Frontmatter does not appear in the rendered output. It is processed as data, not displayed as text.

The --- delimiters must be the first thing in the file. Anything before the opening --- breaks parsing in most tools.

How Static Site Generators Use Frontmatter

Every major SSG handles frontmatter, though the implementation details differ.

Next.js typically uses the gray-matter library to parse .md files. When you read a file from disk, you pass the raw content to matter():

import matter from 'gray-matter'

const fileContents = fs.readFileSync(filePath, 'utf8')
const { data, content } = matter(fileContents)
// data is the frontmatter object
// content is the markdown body

Astro uses Content Collections, which read frontmatter and enforce field types via a Zod schema. You define the expected shape of your frontmatter once, and Astro validates every file in the collection at build time.

Hugo reads frontmatter and supports three formats: YAML (between ---), TOML (between +++), and JSON (between { and }). The rest of the Hugo ecosystem works the same regardless of which format you choose.

Jekyll treats frontmatter as required. Any file without a frontmatter block gets passed through as a static file without template processing. Within templates, the frontmatter is available via the page object: {{ page.title }}, {{ page.date }}.

Eleventy uses gray-matter by default. Frontmatter values become top-level variables in your templates automatically, so a title key in frontmatter is just title in Nunjucks or Liquid templates.

YAML Basics for Frontmatter

Most frontmatter uses a small subset of YAML. Here are the types you'll encounter regularly.

Strings are either quoted or unquoted:

title: "Hello World"
description: A plain unquoted string also works

Quotes are optional unless the string contains special characters like colons, brackets, or curly braces.

Numbers and booleans are unquoted:

year: 2026
published: true
draft: false

Arrays come in two forms. Block syntax places each item on its own line with a dash:

tags:
  - markdown
  - tutorial
  - yaml

Inline syntax keeps everything on one line:

tags: ["markdown", "tutorial", "yaml"]

Both are valid. Pick one and stick to it within a project.

Dates are safest as quoted strings in ISO 8601 format:

date: "2026-01-15"

Some YAML parsers will interpret an unquoted date as a Date object. Others parse it as a string. Quoting the value removes the ambiguity.

Nested objects use indentation:

seo:
  description: "Custom SEO description"
  noindex: false

The nested object becomes a JavaScript object when parsed: data.seo.description.

Common Frontmatter Fields

Different SSGs use different field names, but several fields appear across most projects.

  • title: the page title, usually placed in the HTML <title> tag and the top-level heading
  • date: publication date, used to sort blog posts chronologically
  • draft: true: signals the post should be excluded from production builds in most SSGs
  • slug: a custom URL path override; many SSGs derive the slug from the filename, so this is only needed when you want a different URL
  • excerpt or description: a short summary used in meta descriptions, RSS feeds, and listing pages
  • tags or categories: taxonomy labels that allow grouping and filtering of posts
  • author: the post author's name or a reference ID in a separate authors file
  • image or cover: a path to the featured image, used in social share previews and listing page thumbnails

Not every SSG reads every field by default. If a field isn't built into your SSG, you access it from the frontmatter data object and use it however your template requires.

Schema Validation for Frontmatter

Without validation, frontmatter errors are silent. A missing date field might not break the build but could sort the post incorrectly, or cause a runtime error when a template tries to render it. A typo in a field name (tilte instead of title) won't throw an error; the field just returns undefined.

Astro Content Collections address this directly. You define a Zod schema for your frontmatter, and Astro validates every file in the collection at build time. Missing required fields and type mismatches become errors before the site ever deploys.

For TypeScript-based setups outside of Astro, you can run zod.parse() on the frontmatter object returned by gray-matter:

import { z } from 'zod'

const PostSchema = z.object({
  title: z.string(),
  date: z.string(),
  draft: z.boolean().optional(),
})

const validated = PostSchema.parse(matter(fileContents).data)

If you use a CMS to manage your markdown content, schema enforcement can happen at the editing layer rather than the build layer. The md0 schema builder lets you define which fields a collection has, their types, and whether they're required. That prevents missing or mistyped frontmatter before a file is ever written.

Frontmatter in MDX

MDX files use the same frontmatter syntax as .md files. The --- block must be the first thing in the file; any content before it will break parsing.

Both next-mdx-remote and @mdx-js/mdx support frontmatter parsing. With next-mdx-remote, enable it via the parseFrontmatter option:

const { content, frontmatter } = await compileMDX({
  source,
  options: { parseFrontmatter: true },
})

The frontmatter object then contains all your YAML fields. If you're deciding between .md and .mdx, the MDX vs Markdown post covers the tradeoffs in detail.

Common Mistakes

A few frontmatter errors show up repeatedly.

Forgetting the closing --- causes a parse error in most SSGs. The parser reads to the end of the file looking for the delimiter and either throws or treats the entire file as frontmatter.

Using tabs instead of spaces breaks YAML. YAML requires spaces for indentation. Tabs are not valid, and the error message isn't always clear about the cause.

Special characters without quotes cause parse errors. Colons, curly braces, and brackets have meaning in YAML. If your string contains any of them, wrap the value in quotes:

# This breaks
title: Hello: World

# This works
title: "Hello: World"

Inconsistent date handling creates subtle bugs. Some YAML parsers convert unquoted dates to Date objects; others return strings. Quoting all dates makes behavior consistent across environments.

Getting frontmatter right is mostly about understanding the YAML rules and knowing what fields your SSG or build pipeline expects. Once the structure is clear, frontmatter becomes straightforward to write and maintain.

Ready to try md0.io?

Start writing beautiful markdown today with our free tools.

Markdown Frontmatter — What It Is and How to Use It | md0