Gem that validates the front matter of your Jekyll posts/pages and warns (or breaks the build) when something is wrong.
- Required field missing
- Wrong type (
datethat isn't a date,tagsthat isn't an array...) - Value outside an allowed list (
layoutis invalid, for example) - Field that should be a slug (no accents, no spaces, no uppercase) but isn't
- Field that should be a slug_file (filename with slug-like name) but isn't
- Field whose value should have a matching asset on disk
(e.g.
cover_image: "cute-cats"should exist atassets/images/cute-cats.jpg) but the file doesn't exist - Nested hash validation with
keysproperty and dot notation - Runs automatically on
jekyll buildandjekyll serve - Standalone CLI for git
pre-commithooks
Get up and running in 5 minutes:
# 1. Add to your site's Gemfile
cat >> Gemfile <<'EOF'
group :jekyll_plugins do
gem "jekyll-front-matter-validator", git: "https://github.com/brendaw/jekyll-front-matter-validator", tag: "v0.3.0"
end
EOF
# 2. Install
bundle install
# 3. Add validation rules to _config.yml (see examples/site-integration/_config.yml.example)
# 4. Build — validation runs automatically
bundle exec jekyll buildIf anything is wrong with your front matter, the build will stop and show
exactly what needs to be fixed. Set fail_build_on_error: false in
_config.yml to warn without breaking the build.
You can also validate files directly with the CLI:
bundle exec fmv-validate # everything in the project
bundle exec fmv-validate --staged # only git-staged files
bundle exec fmv-validate _posts/*.md # specific filesSee Installation for alternative install methods (git, vendored) and Schema reference for the full configuration reference.
lib/
jekyll-front-matter-validator.rb # entry point
jekyll/front_matter_validator/
version.rb
core.rb # all validation logic (no Jekyll dependency)
jekyll_hook.rb # hook :site, :pre_render (only loads if Jekyll exists)
exe/
fmv-validate # standalone CLI
examples/site-integration/ # files to copy into YOUR SITE's repo
Gemfile.example
_config.yml.example
.githooks/pre-commit
bin/install-git-hooks.sh
In the site's Gemfile (see examples/site-integration/Gemfile.example):
group :jekyll_plugins do
gem "jekyll-front-matter-validator", git: "https://github.com/brendaw/jekyll-front-matter-validator", tag: "v0.3.0"
endbundle installClone or copy the gem into the site repo:
cp -r jekyll-front-matter-validator vendor/jekyll-front-matter-validatorIn the site's Gemfile:
group :jekyll_plugins do
gem "jekyll-front-matter-validator", path: "vendor/jekyll-front-matter-validator"
endbundle installNo extra steps needed — Bundler loads the gem, and the
:site, :pre_render hook runs on both jekyll build and
jekyll serve (including on every --watch regeneration).
bundle exec jekyll build
# or
bundle exec jekyll serveIf something is invalid and fail_build_on_error: true (default), the
build stops with output like:
FrontMatterValidator: 3 issue(s) found in front matter
[ERROR] _posts/2026-01-06-post.md -> slug: expected type 'slug' (expected a slug-like value, e.g. 'my-slug', no spaces/uppercase), got "My Post!"
[ERROR] _posts/2026-01-06-post.md -> cover_image: no matching asset found at 'assets/images/cute-cats.{jpg,jpeg,png,webp}'
[ERROR] _posts/2026-01-06-post.md -> layout: value "article" not in allowed list ["post", "article"]
To warn without breaking the build, use fail_build_on_error: false.
Copy the .githooks/ folder and bin/install-git-hooks.sh from
examples/site-integration/ into the site repo, then:
./bin/install-git-hooks.shThis sets core.hooksPath to .githooks/. From then on, every
git commit runs bundle exec fmv-validate --staged, validating only
staged files, and blocks the commit if anything is wrong.
To disable: git config --unset core.hooksPath.
bundle exec fmv-validate # validates everything in the project
bundle exec fmv-validate --staged # only git-staged files
bundle exec fmv-validate path.md # specific file(s)front_matter_schema:
fail_build_on_error: true # false = warn only, don't break the build
defaults: # applied to everything not matched by collections
required: [title]
types: { title: string }
collections:
posts:
path: _posts # path prefix that identifies this collection
required: [title, date, slug]
types:
date: date
tags: array
slug: slug
cover:
type: hash
keys:
url: string
width: integer
enum:
layout: [post, article]
assets:
cover_image:
dir: assets/images
extensions: [jpg, jpeg, png, webp]
slug:
pattern: "assets/posts/{value}/cover.*"
slugify: true
cover.url: # dot notation for nested fields
dir: assets/images
extensions: [jpg, jpeg, png, webp]string, integer, float, boolean, array, hash, date, slug, slug_file.
slug validates against /\A[a-z0-9]+(-[a-z0-9]+)*\z/ — i.e. lowercase
letters, digits, and hyphens only, no accents or spaces.
slug_file validates the filename part (before extension) as a slug.
Accepts uppercase letters and underscores in addition to lowercase
letters, digits, and hyphens. E.g. my-post.jpg, My_Cool_Post.png
are valid; My Post.jpg and cover!.jpg are not.
hash supports nested validation. You can declare sub-keys with the
keys property (explicit style) or dot notation (compact style) — both
can be mixed in the same config:
types:
# Explicit style
cover:
type: hash
keys:
url: string
width: integer
author:
type: hash
keys:
name: string
user: string
# Dot notation (same result, more compact)
# cover.url: string
# cover.width: integer
# cover.author.name: string
# cover.author.user: stringExtra keys present in the hash but not declared in keys are accepted
without errors — validation only checks the fields you explicitly list.
For each field listed in assets, the validator builds an expected path
from the field value and checks whether a matching file exists. Two
configuration styles:
dir + extensions (simpler, a file directly in a folder):
assets:
cover_image:
dir: assets/images
extensions: [jpg, jpeg, png, webp]cover_image: "cute-cats" → looks for assets/images/cute-cats.{jpg,jpeg,png,webp}.
pattern (more flexible, with {value} as a placeholder — useful
for subdirectory structures):
assets:
slug:
pattern: "assets/posts/{value}/cover.*"slug: "my-post" → looks for assets/posts/my-post/cover.*.
In both cases, slugify: true normalizes the field value (strips
accents, downcases, replaces spaces with hyphens) before building the
path — useful when the field used to derive the filename isn't itself a
slug (e.g. using title to find the image).
Dot notation is supported for nested front matter fields:
assets:
cover.url:
dir: assets/images
extensions: [jpg, jpeg, png, webp]cover: { url: "cute-cats" } → looks for assets/images/cute-cats.{jpg,jpeg,png,webp}.
Dot notation works with both dir + extensions and pattern modes,
and can be combined with slugify: true.
For each file, the validator looks in front_matter_schema.collections
for an entry whose path is a prefix of the file's path (e.g. _posts
matches _posts/2026-01-05-hello.md). If none matches, it uses
front_matter_schema.defaults. The defaults rules are always merged
with the matched collection's rules (required fields are summed,
types/enums/assets are combined with the defaults).
For types, nested hash definitions are deep-merged — if both
defaults and a collection define the same hash field with different
sub-keys, all sub-keys are preserved (collection keys win on conflicts).
bundle install
bundle exec rspecTests cover the core validation logic in lib/jekyll/front_matter_validator/core.rb
(slugify, required/type/enum validation, asset checking, rule matching, YAML parsing).
Contributions are welcome — bug fixes, new features, documentation improvements, and translations. See CONTRIBUTING.md for the development workflow.
The project uses GitHub Actions for CI (gem build, RuboCop linting, and unit tests across Ruby 2.7, 3.2, and 3.3).
Issues and Pull Requests are open for your contribution.
See the AUTHORS file for the amazing contributors of this project.
MIT — William Brendaw and the contributors — 2026