Skip to content

#339: Parse Markdown Metadata With Frontmatter

Frontmatter is the metadata format for Markdown and YAML that we can use in MkDocs and many other tools. While we could parse the metadata on our own, tools like Python Frontmatter are a much simpler way to do it. Let us see how this works.

Installation

We can install the package Python Frontmatter with this command:

uv pip install python-frontmatter

Read Markdown files

A Markdown file with frontmatter metadata can look like this:

---
title: Hello, frontmatter
date: 2026-07-10
tags:
  - python
  - markdown
draft: false
---

# Hello

This is the body of the post.

When we want to read it with frontmatter, we can use this code snippet:

1
2
3
4
5
6
import frontmatter

post = frontmatter.load("frontmatter_sample.md")
print(f"title: '{post["title"]}'")
print(f"tags: '{post["tags"]}'")
print(post.content)

When we run the code, we should get this output:

title: 'Hello, frontmatter'
tags: '['python', 'markdown']'
# Hello

This is the body of the post.

Read Markdown strings

If our Markdown is already in a string, we can use it directly in frontmatter without the detour of writing a file:

text = """---
title: In-memory post
tags:
  - python
  - cms
draft: True
---

This content came from a string.
"""

post = frontmatter.loads(text)

print(f"title: '{post["title"]}'")
print(f"tags: '{post["tags"]}'")
print(post.content)

The output in this case looks like this:

title: 'In-memory post'
tags: '['python', 'cms']'
This content came from a string.

Access the metadata

Frontmatter gives us multiple ways to access the metadata. We can use the metadata property directly and get a dictionary of all metadata back, or we use one of the helpful proxy methods:

1
2
3
4
5
6
7
8
9
title = post["title"]
tags = post.get("tags", [])
draft = post.get("draft", False)
metadata = post.metadata

print(f"title: '{title}'")
print(f"tags: '{tags}'")
print(f"draft: '{draft}'")
print(f"metadata: '{metadata}'")

When we run this code against our in-memory string, we get this output:

title: 'In-memory post'
tags: '['python', 'cms']'
draft: 'True'
metadata: '{'title': 'In-memory post', 'tags': ['python', 'cms'], 'draft': True}'

Be aware that the dictionary approach only works when the key is present. If we access a non-existing hello key, we get this exception:

KeyError: 'hello'

To prevent this, we should take the way through the get() method and set a default value that will be used should the key be missing:

print(f"hello: {post.get("hello", "world")}") 
# returns `hello: world` if "hello" key is missing

Modify metadata

Since the metadata is a dictionary, we can modify metadata as we would change values in any other dictionary:

1
2
3
4
post["release"] = "2026-07-10 20:00"
post["tags"].append("markdown")
markdown = frontmatter.dumps(post)
print(markdown)

This will set us a release date and add markdown to the tags list. We can dump the value of the post object into a string and then save it back to the file system:

---
draft: true
release: 2026-07-10 20:00
tags:
- python
- cms
- markdown
title: In-memory post
---

This content came from a string.

Build Markdown from code

We can also create a new object in Python and turn it into markdown with this code:

post = frontmatter.Post(
    "# New post\n\nThis post was generated with Python.",
    title="Generated post",
    date="2026-07-10",
    tags=["python", "automation"],
    draft=False,
)

markdown = frontmatter.dumps(post)

print(markdown)

This gives us this nicely formatted frontmatter Markdown:

---
date: '2026-07-10'
draft: false
tags:
- python
- automation
title: Generated post
---

# New post

This post was generated with Python.

Next

Thanks to the frontmatter library we can access Markdown metadata through a dictionary and save ourselves the cumbersome work of writing a parser. This will be especially useful when we try to improve Markdown files in an Obsidian vault. How we can do that will be the topic of next week's post.