Frontmatter ¶
Frontmatter is the metadata block at the top of your Markdown files that tells markata-go how to process, display, and organize your content. This guide covers everything you need to know about using frontmatter effectively.
Table of Contents ¶ #
- What is Frontmatter?
- Basic Frontmatter Fields
- Complete Field Reference
- Custom Fields (Extra)
- Examples
- Common Patterns
- Frontmatter in Filtering
What is Frontmatter? ¶ #
Frontmatter is YAML metadata placed at the very top of a Markdown file, enclosed between two lines of three dashes (---). It defines properties like the post’s title, date, tags, and any custom data you want to associate with the content.
Visual Example ¶ #
---
title: "Getting Started with Go"
date: 2024-01-15
published: true
tags:
- go
- tutorial
- beginners
description: "A beginner-friendly introduction to Go programming."
---
# Welcome to Go!
This is where your actual content begins...
How markata-go Parses Frontmatter ¶ #
When markata-go processes your Markdown files, it:
- Detects the opening delimiter - Looks for
---at the very start of the file - Extracts the YAML block - Reads everything until the closing
--- - Parses the YAML - Converts the metadata into structured data
- Separates the content - Everything after the second
---becomes the post body
Important notes:
- The opening
---must be on the very first line of the file - The closing
---must be on its own line - Content before the opening
---or malformed delimiters will cause errors - Files without frontmatter are valid - they’ll use default values
Valid Frontmatter ¶ #
---
title: My Post
---
Content here
Invalid Frontmatter (Missing Closing Delimiter) ¶ #
---
title: My Post
Content here (ERROR: unclosed frontmatter)
No Frontmatter (Also Valid) ¶ #
# My Post
Content starts immediately - defaults will be used.
Basic Frontmatter Fields ¶ #
These are the core fields that markata-go recognizes and uses directly.
title (string) ¶ #
The display title of your post.
title: "Understanding Goroutines"
- Type: string
- Default: None (derived from filename if not set)
- Used for: Page title,
<title>tag, feed listings, slug generation
If not provided, the slug is derived from the filename instead.
slug (string) ¶ #
The URL-safe identifier for your post. Determines the URL path.
slug: "understanding-goroutines"
- Type: string
- Default: Auto-generated from
title(or filename if no title) - Used for: URL path (
/understanding-goroutines/)
Auto-generation rules:
- Converts to lowercase
- Replaces spaces with hyphens
- Removes special characters
- Collapses multiple hyphens
# These titles produce these slugs:
title: "Hello World!" # slug: hello-world
title: "Go's Best Features" # slug: gos-best-features
title: "Part 1: The Basics" # slug: part-1-the-basics
Custom Slugs ¶ #
You can explicitly set any slug in frontmatter:
# Simple custom slug
slug: my-custom-url
# Nested path (creates /docs/api/overview/)
slug: docs/api/overview
# Leading/trailing slashes are automatically stripped
slug: /about/ # becomes: about
slug: /docs/guides/ # becomes: docs/guides
Homepage from Custom Slug ¶ #
Any post can become the homepage by setting an empty or slash slug:
---
title: "Welcome to My Site"
slug: "" # This post becomes the homepage at /
published: true
---
Welcome to my site!
Or equivalently:
---
title: "Welcome to My Site"
slug: / # Also becomes the homepage at /
published: true
---
This is useful when you want a custom homepage from content in any directory, rather than using a feed or index.md at the root.
Note: If multiple posts have the same slug (including empty slug for homepage), the build will fail with a path conflict error to prevent accidental overwrites.
Special Case: index.md Files ¶ #
Files named index.md automatically generate slugs based on their directory:
| File Path | Auto-Generated Slug | URL |
|---|---|---|
./index.md |
"" (empty) |
/ (homepage) |
docs/index.md |
docs |
/docs/ |
blog/guides/index.md |
blog/guides |
/blog/guides/ |
This allows directory-based URL structures without needing to set explicit slugs.
date (date) ¶ #
The publication date of the post.
date: 2024-01-15
- Type: date (YYYY-MM-DD format recommended)
- Default: None
- Used for: Sorting, display, feeds, scheduled publishing
Supported date formats:
markata-go supports a wide variety of date formats for maximum flexibility:
# ISO 8601 formats (recommended)
date: 2024-01-15 # Date only
date: 2024-01-15T10:30:00 # With time
date: 2024-01-15T10:30:00Z # With UTC timezone
date: 2024-01-15T10:30:00+05:00 # With timezone offset
date: 2024-01-15 10:30:00 # With space separator
date: 2024-01-15 10:30 # Without seconds
# Single-digit hours (automatically normalized)
date: 2024-01-15 1:30:00 # 1am
date: 2024-01-15 9:30:00 # 9am
# Slash-separated dates
date: 2024/01/15 # YYYY/MM/DD
date: 2024/01/15 10:30:00 # With time
date: 01/15/2024 # MM/DD/YYYY (US format)
# Named month formats
date: January 15, 2024 # Full month name
date: Jan 15, 2024 # Abbreviated month
date: 15 January 2024 # Day first
date: 15 Jan 2024 # Day first, abbreviated
# European format
date: 15-01-2024 # DD-MM-YYYY
Note: Malformed time components are automatically corrected. For example, 2024-01-15 8:011:00 (typo) will be parsed as 2024-01-15 08:11:00.
published (boolean) ¶ #
Whether the post should be included in public feeds and listings.
published: true
- Type: boolean
- Default:
false - Used for: Filtering posts in feeds, sitemaps, and RSS
Shadow Pages: Posts with published: false are still rendered to HTML and accessible via direct URL. They become “shadow pages” - accessible but not discoverable through normal site navigation.
published |
Behavior |
|---|---|
true |
Rendered + included in feeds, sitemaps, RSS |
false |
Rendered as “shadow page” + NOT in feeds, sitemaps, RSS |
Use cases for shadow pages:
- Draft content accessible to reviewers via direct URL
- Private documentation not linked publicly
- Work-in-progress content shared with specific people
- Admin pages or staging content
Accepted values:
published: true # or: yes, on
published: false # or: no, off
draft (boolean) ¶ #
Marks the post as a work-in-progress that should NOT be rendered at all.
draft: true
- Type: boolean
- Default:
false - Used for: Truly private content that shouldn’t be accessible
Important: Posts with draft: true are never rendered to HTML. Use this for content you don’t want accessible at all, even via direct URL.
draft |
Behavior |
|---|---|
false |
Content is rendered (visibility depends on published) |
true |
Content is NOT rendered at all |
Comparison with published:
| Scenario | published |
draft |
Rendered? | In Feeds? |
|---|---|---|---|---|
| Public post | true |
false |
Yes | Yes |
| Shadow page | false |
false |
Yes | No |
| Private WIP | any | true |
No | No |
tags (list) ¶ #
A list of tags for categorizing the post.
tags:
- go
- concurrency
- tutorial
Or inline format:
tags: [go, concurrency, tutorial]
- Type: list of strings
- Default:
[](empty list) - Used for: Filtering, tag pages, SEO, organization
description (string) ¶ #
A brief summary of the post content.
description: "Learn how to use goroutines for concurrent programming in Go."
- Type: string
- Default: Auto-generated from first ~160 characters of content
- Used for: Meta description, SEO, social previews
Note: The description field is no longer used for article card excerpts in feeds. Excerpts are now auto-generated from your content (first 3 paragraphs or 1500 characters). However, description may still be used for SEO meta tags and RSS feeds.
Auto-generation: If not provided, markata-go extracts the first paragraph or ~160 characters from your content (with HTML tags stripped).
template (string) ¶ #
The HTML template file to use for rendering this post.
template: "tutorial.html"
- Type: string
- Default:
"post.html" - Used for: Custom layouts per post
Templates are looked up in:
templates/directory in your project- Theme templates
- Default theme fallback
Complete Field Reference ¶ #
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
title |
string | None | No | Display title of the post |
slug |
string | Auto-generated | No | URL path identifier |
date |
date | None | No | Publication date (YYYY-MM-DD) |
published |
bool | false |
No | Whether to include in public feeds |
draft |
bool | false |
No | Whether this is a work-in-progress |
tags |
[]string | [] |
No | List of categorization tags |
description |
string | Auto-generated | No | Brief summary for SEO/meta tags |
template |
string | "post.html" |
No | Template file to use for rendering |
skip |
bool | false |
No | Skip this file during processing entirely |
authors |
[]string | [] |
No | List of author IDs (multi-author support) |
author |
string | None | No | Legacy single-author name or ID |
by |
string | None | No | Alias for author |
writer |
string | None | No | Alias for author |
Field Details ¶ #
skip ¶ #
Completely exclude a file from processing:
skip: true
Use this for files you want to keep in your content directory but never process (notes, drafts not ready for review, etc.).
authors ¶ #
Assign one or more authors to a post by referencing author IDs defined in your site configuration:
---
title: "Collaborative Article"
authors:
- waylon
- guest
---
Each author ID is resolved against the [markata-go.authors.authors] config map during the build. Resolved author objects (with name, avatar, role, bio, etc.) are available in templates as post.author_objects.
You can use the extended format to specify per-post roles and details for each author:
---
title: "Collaborative Article"
authors:
- id: waylon
role: author
details: wrote the introduction and conclusion
- id: codex
role: pair programmer
details: wrote the code examples
- id: kimmi
role: outliner
---
The role overrides the author’s config-level role for this post only. The details field provides a short description of what the author did on this specific post, displayed as a tooltip when hovering over the author’s name in the byline. Both fields are optional and can be used independently.
Key aliases: For convenience, the extended format supports aliases so you can use whichever key name feels natural:
| Canonical Key | Aliases |
|---|---|
id |
name, handle |
role |
job, position, part, title |
details |
detail, description |
For example, these are all equivalent:
---
authors:
- name: waylon
title: maintainer
description: built the feature
# same as:
- id: waylon
role: maintainer
details: built the feature
---
The canonical key always takes precedence when both it and an alias are present.
You can mix simple string IDs and extended format in the same array:
---
authors:
- waylon
- id: codex
role: editor
details: reviewed and edited the draft
---
If no authors or author field is set, the default author from config (the one with default = true) is assigned automatically.
author ¶ #
Legacy single-author field. Use this for simple sites or backward compatibility:
---
title: "Solo Post"
author: "Jane Doe"
---
Aliases: The by and writer frontmatter fields are aliases for author. This allows more natural frontmatter syntax:
---
title: "Solo Post"
by: "Jane Doe"
---
---
title: "Solo Post"
writer: "Jane Doe"
---
All three (author, by, writer) set the same author field. Priority order: authors > author > by > writer. Only the first match is used.
Priority: If both authors and author (or its aliases) are set on the same post, authors takes precedence.
Related: See Authors Configuration for defining author profiles.
Custom Fields (Extra) ¶ #
Any frontmatter field that isn’t a built-in field is automatically stored in the Extra map. This allows you to add any custom metadata to your posts.
Adding Custom Fields ¶ #
---
title: "Building a REST API"
date: 2024-01-15
published: true
# Custom fields - stored in Extra
author: "Jane Doe"
category: "Backend"
series: "API Development"
series_order: 1
featured: true
cover_image: "/images/api-cover.jpg"
difficulty: "intermediate"
reading_time: "8 min"
---
Accessing Custom Fields in Templates ¶ #
Custom fields are available via post.Extra in templates:
{% if post.Extra.featured %}
<span class="badge">Featured</span>
{% endif %}
{% if post.Extra.author %}
<p class="author">By {{ post.Extra.author }}</p>
{% endif %}
{% if post.Extra.cover_image %}
<img src="{{ post.Extra.cover_image }}" alt="{{ post.Title }}">
{% endif %}
{% if post.Extra.series %}
<div class="series-info">
Part {{ post.Extra.series_order }} of {{ post.Extra.series }}
</div>
{% endif %}
Common Custom Field Use Cases ¶ #
Author Information ¶ #
author: "Jane Doe"
author_email: "[email protected]"
author_url: "https://janedoe.dev"
author_avatar: "/images/avatars/jane.jpg"
Series/Collections ¶ #
series: "Building a Blog with Go"
series_order: 3
series_total: 5
Visual Elements ¶ #
cover_image: "/images/posts/my-cover.jpg"
thumbnail: "/images/posts/my-thumb.jpg"
og_image: "/images/social/my-post-og.jpg"
hero_video: "https://youtube.com/watch?v=..."
Content Metadata ¶ #
difficulty: "beginner" # beginner, intermediate, advanced
reading_time: "5 min read"
word_count: 1250
updated: 2024-02-20
revision: 3
Categorization ¶ #
category: "Tutorials"
subcategory: "Web Development"
topic: "Go Programming"
Flags and Features ¶ #
featured: true
pinned: true
sponsored: false
comments_enabled: true
toc: true # Enable table of contents
math: true # Enable math rendering
mermaid: true # Enable diagrams
External Links ¶ #
canonical_url: "https://example.com/original-post"
github_repo: "https://github.com/user/project"
demo_url: "https://demo.example.com"
Aliases (for Wikilinks) ¶ #
Define alternative names that can be used in wikilinks to link to this post:
---
title: "ECMAScript Language Specification"
slug: "ecmascript"
aliases:
- js
- javascript
- JavaScript
---
With these aliases, any of the following wikilinks will resolve to this post:
[[ecmascript]]- matches the slug[[js]]- matches an alias<a href="/tags/javascript/" class="wikilink" data-title="Posts tagged: javascript" data-description="All posts with the tag "javascript"">Posts tagged: javascript</a>- matches an alias<a href="/tags/javascript/" class="wikilink" data-title="Posts tagged: javascript" data-description="All posts with the tag "javascript"">Posts tagged: javascript</a>- case-insensitive alias match
Slug Precedence
If another post has slug: "javascript", then <a href="/tags/javascript/" class="wikilink" data-title="Posts tagged: javascript" data-description="All posts with the tag "javascript"">Posts tagged: javascript</a> will link to
that post (slug match) rather than the post with the alias. Slugs always take
precedence over aliases.
Alias field synonyms: The frontmatter loader also accepts alias, handle, and handles and normalizes them into aliases.
Examples ¶ #
Minimal Frontmatter ¶ #
The absolute minimum for a publishable post:
---
title: "Hello World"
published: true
---
Your content here.
Full Frontmatter Example ¶ #
A comprehensive example using all built-in fields:
---
title: "Complete Guide to Error Handling in Go"
slug: "go-error-handling-guide"
date: 2024-01-15
published: true
draft: false
tags:
- go
- error-handling
- best-practices
- tutorial
description: "Master error handling in Go with this comprehensive guide covering best practices, custom errors, and common patterns."
template: "tutorial.html"
---
Content begins here...
Blog Post Example ¶ #
A typical blog post with custom fields:
---
title: "Why I Switched from Python to Go"
date: 2024-01-15
published: true
tags:
- go
- python
- opinion
- programming
description: "My journey from Python to Go and the lessons learned along the way."
# Custom fields
author: "Alex Chen"
category: "Opinion"
featured: true
cover_image: "/images/python-to-go.jpg"
reading_time: "6 min read"
---
# Why I Switched from Python to Go
After 5 years of Python development, I made the switch to Go...
Documentation Page Example ¶ #
For technical documentation:
---
title: "Configuration Reference"
slug: "docs/configuration"
published: true
tags:
- documentation
- reference
description: "Complete reference for markata-go configuration options."
template: "docs.html"
# Custom fields
section: "Reference"
order: 10
toc: true
prev_page: "/docs/getting-started/"
next_page: "/docs/plugins/"
---
# Configuration Reference
This page documents all available configuration options...
Landing Page Example ¶ #
For standalone pages with custom layouts:
---
title: "Welcome to My Site"
slug: ""
published: true
template: "landing.html"
# Custom fields
hero_title: "Build Faster with Go"
hero_subtitle: "A static site generator that respects your time"
cta_text: "Get Started"
cta_url: "/docs/getting-started/"
features:
- title: "Fast Builds"
description: "Compile thousands of pages in seconds"
icon: "lightning"
- title: "Plugin System"
description: "Extend functionality with Go plugins"
icon: "puzzle"
- title: "Feed Generation"
description: "RSS, Atom, and JSON feeds built-in"
icon: "rss"
---
Additional content for the landing page...
Tutorial with Series ¶ #
For posts that are part of a series:
---
title: "Building a CLI Tool - Part 2: Adding Commands"
date: 2024-01-20
published: true
tags:
- go
- cli
- tutorial
description: "Learn how to add subcommands to your Go CLI application using cobra."
template: "tutorial.html"
# Series information
series: "Building a CLI Tool in Go"
series_slug: "go-cli-tutorial"
series_order: 2
series_total: 5
# Tutorial metadata
difficulty: "intermediate"
prerequisites:
- "Basic Go knowledge"
- "Part 1 of this series"
code_repo: "https://github.com/example/go-cli-tutorial"
---
# Adding Commands
In the previous part, we set up our project structure...
Media Fields ¶ #
The image and video frontmatter fields can be used interchangeably in photo and video card templates. The system auto-detects whether a URL points to a video or image based on the file extension.
Recognized Video Extensions ¶ #
.mp4, .webm, .mov, .m4v, .ogv, .ogg (case-insensitive)
Any other extension (or no extension) is treated as an image.
How It Works ¶ #
Card templates use two filters to resolve media:
media_url– picks the first non-empty value betweenimageandvideofieldsis_video– checks the file extension to decide whether to render<video>or<img>
This means all of the following work equally well for a photo card:
# Option A: image field with a video file
---
image: "/media/demo.mp4"
---
# Option B: video field with a video file
---
video: "/media/demo.mp4"
---
# Option C: image field with an image file
---
image: "/photos/sunset.jpg"
---
# Option D: both fields (image takes priority)
---
image: "/photos/sunset.jpg"
video: "/media/demo.mp4"
---
Using Media Fields in Custom Templates ¶ #
You can use the is_video and media_url filters in your own templates:
{% with post.image|media_url:post.video as media_src %}
{% if media_src %}
{% if media_src|is_video %}
<video src="{{ media_src }}" autoplay muted loop playsinline></video>
{% else %}
<img src="{{ media_src }}" alt="{{ post.title }}">
{% endif %}
{% endif %}
{% endwith %}
Common Patterns ¶ #
Draft Workflow ¶ #
Use draft and published together for a clear workflow:
# Work in progress - not visible anywhere
---
title: "My Draft Post"
draft: true
published: false
---
# Ready for review - still not public
---
title: "My Draft Post"
draft: true
published: false
---
# Published - visible to everyone
---
title: "My Draft Post"
draft: false
published: true
---
Feed filter for published only:
[[markata-go.feeds]]
slug = "blog"
filter = "published == True"
Feed filter excluding drafts:
[[markata-go.feeds]]
slug = "blog"
filter = "published == True and draft == False"
Scheduled Publishing ¶ #
Combine date with feed filters for scheduled publishing:
---
title: "New Year Announcement"
date: 2025-01-01
published: true
---
Feed filter for past/current dates only:
[[markata-go.feeds]]
slug = "blog"
filter = "published == True and date <= today"
Posts with future dates won’t appear until that date arrives. Rebuild your site daily (via CI/CD) to “publish” scheduled posts.
Custom Templates Per Post ¶ #
Use different layouts for different types of content:
# Regular blog post
---
title: "My Post"
template: "post.html"
---
# Tutorial with sidebar
---
title: "Go Tutorial"
template: "tutorial.html"
---
# Full-width landing page
---
title: "About Me"
template: "landing.html"
---
# Documentation with navigation
---
title: "API Reference"
template: "docs.html"
---
Series of Posts ¶ #
Organize related posts into a series:
Post 1:
---
title: "Web Scraping with Go - Part 1: Basics"
date: 2024-01-10
series: "Web Scraping with Go"
series_order: 1
tags: [go, web-scraping, tutorial]
---
Post 2:
---
title: "Web Scraping with Go - Part 2: Handling JavaScript"
date: 2024-01-17
series: "Web Scraping with Go"
series_order: 2
tags: [go, web-scraping, tutorial]
---
Filter for the series:
[[markata-go.feeds]]
slug = "series/web-scraping-go"
title = "Web Scraping with Go"
filter = "Extra.series == 'Web Scraping with Go'"
sort = "Extra.series_order"
reverse = false
Canonical URLs for Cross-Posted Content ¶ #
When you publish the same content elsewhere:
---
title: "My Post"
canonical_url: "https://dev.to/username/my-post"
---
Use in templates:
{% if post.Extra.canonical_url %}
<link rel="canonical" href="{{ post.Extra.canonical_url }}">
{% else %}
<link rel="canonical" href="{{ config.URL }}{{ post.Href }}">
{% endif %}
Frontmatter in Filtering ¶ #
Frontmatter fields power the feed filtering system. You can filter posts based on any frontmatter value.
Filter Syntax ¶ #
Filters use a Python-like expression syntax:
filter = "published == True"
filter = "'tutorial' in tags"
filter = "date >= '2024-01-01'"
Filtering by Built-in Fields ¶ #
# Published posts only
filter = "published == True"
# Exclude drafts
filter = "draft == False"
# Posts with a specific tag
filter = "'go' in tags"
# Posts from 2024
filter = "date >= '2024-01-01' and date < '2025-01-01'"
# Posts with a specific slug prefix
filter = "slug.startswith('tutorials/')"
# Posts using a specific template
filter = "template == 'tutorial.html'"
Filtering by Custom Fields (Extra) ¶ #
Access custom fields directly by name:
# Featured posts
filter = "featured == True"
# Posts by specific author
filter = "author == 'Jane Doe'"
# Posts in a specific category
filter = "category == 'Tutorials'"
# Posts in a series
filter = "series == 'Web Scraping with Go'"
# Intermediate difficulty tutorials
filter = "difficulty == 'intermediate'"
Combined Filters ¶ #
Use and, or, and parentheses for complex filters:
# Published tutorials
filter = "published == True and 'tutorial' in tags"
# Featured posts from 2024
filter = "published == True and featured == True and date >= '2024-01-01'"
# Go or Python tutorials
filter = "published == True and ('go' in tags or 'python' in tags)"
# Published, non-draft, with specific category
filter = "published == True and draft == False and category == 'Backend'"
Filtering with Dates ¶ #
Special date values are available:
# Posts up to today (no future posts)
filter = "date <= today"
# Posts from the last 30 days
filter = "date >= today - 30"
# Recent posts (within current timestamp)
filter = "date <= now"
Filter Operators Reference ¶ #
| Operator | Description | Example |
|---|---|---|
== |
Equal to | published == True |
!= |
Not equal to | draft != True |
> |
Greater than | date > '2024-01-01' |
>= |
Greater than or equal | date >= '2024-01-01' |
< |
Less than | date < '2025-01-01' |
<= |
Less than or equal | date <= today |
in |
Value in collection | 'go' in tags |
and |
Logical AND | published == True and featured == True |
or |
Logical OR | 'go' in tags or 'python' in tags |
not |
Logical NOT | not draft |
String Methods in Filters ¶ #
# Slugs starting with a prefix
filter = "slug.startswith('tutorials/')"
# Slugs ending with a suffix
filter = "slug.endswith('-guide')"
# Slugs containing a substring
filter = "slug.contains('api')"
# Case-insensitive comparison
filter = "title.lower().contains('go')"
Complete Filter Examples ¶ #
Home page - recent published posts:
[[markata-go.feeds]]
slug = ""
title = "Recent Posts"
filter = "published == True and date <= today"
sort = "date"
reverse = true
items_per_page = 5
Tutorials section:
[[markata-go.feeds]]
slug = "tutorials"
title = "Tutorials"
filter = "published == True and 'tutorial' in tags"
sort = "date"
reverse = true
Featured posts:
[[markata-go.feeds]]
slug = "featured"
title = "Featured"
filter = "published == True and featured == True"
sort = "date"
reverse = true
items_per_page = 6
Author archive:
[[markata-go.feeds]]
slug = "authors/jane-doe"
title = "Posts by Jane Doe"
filter = "published == True and author == 'Jane Doe'"
sort = "date"
reverse = true
Beginner-friendly content:
[[markata-go.feeds]]
slug = "beginners"
title = "Beginner Guides"
filter = "published == True and difficulty == 'beginner'"
sort = "date"
reverse = true
Next Steps ¶ #
Now that you understand frontmatter, here are recommended next steps based on what you want to do:
Organize your content with feeds:
- Feeds Guide - Learn how to create filtered, sorted collections using your frontmatter fields
Customize how your content looks:
- Templates Guide - Use frontmatter values in your templates
- Themes Guide - Style your site with color palettes and custom CSS
Learn about Markdown features:
- Markdown Guide - Advanced Markdown syntax and extensions
See Also ¶ #
- Feeds Guide - Complete guide to feed filtering and configuration
- Templates Guide - Using frontmatter in templates
- Configuration Guide - Site-wide defaults and settings
- Quick Reference - Frontmatter snippets and cheat sheets