Jekyll Custom Collections: Modeling Structured Content Without Forcing It Into Posts
Posts are chronological and data files lack URLs. Jekyll custom collections let you model staff bios, product pages, and docs as first-class documents with their own templates and Liquid access.
26 Aug 2025, 01:09 UTC

You have a team page, a product catalog, or a set of documentation pages that do not belong in reverse-chronological order. Jekyll posts assume time matters. Data files in _data/ hold structured records but do not naturally become pages with their own URLs and layouts. Custom collections close that gap by treating your content as first-class documents.
What a Custom Collection Actually Is
A collection is a folder of Markdown or HTML files that Jekyll reads during every build. You declare it in _config.yml, and each file carries YAML front matter—the metadata block at the top of the file—just like a post. Jekyll exposes the whole set to Liquid, its templating language, through site.<collection_name>, so you can loop over staff members, products, or docs in any template.
Because collections are core Jekyll behavior rather than plugins, they work in a standard jekyll build and in safe mode, which skips custom plugins. That matters when a hosting platform restricts plugins: your collection still builds, though the Jekyll version that platform uses may differ from your local one.
The Key Decision: Output or Data Only
Every collection has an output setting. Set it to true and each document renders through a layout to its own URL. Set it to false and the documents exist purely as data for Liquid to consume—useful for reusable records that should never appear as standalone pages.
This is not a cosmetic choice. Enabling output: true multiplies generated pages. For a large record set, that increases build time and the size of your _site directory. If you only need to list the data on one page, keep output: false and iterate in Liquid instead.
Worked Example: A Staff Directory
Suppose you want a page per employee plus an index. First, declare the collection in _config.yml:
collections:
staff:
output: true
permalink: /team/:title/
Create a file at _staff/jane-doe.md with front matter and body content:
---
layout: staff
name: Jane Doe
role: Platform Engineer
---
Jane maintains the build pipeline and internal tooling.
In a template, you can render the index by sorting explicitly. Do not assume Jekyll returns documents in a predictable order:
<ul>
{% assign sorted = site.staff | sort: 'name' %}
{% for person in sorted %}
<li><a href="{{ person.url }}">{{ person.name }}</a> — {{ person.role }}</li>
{% endfor %}
</ul>
The person.url value depends on your permalink template and Jekyll version. Rather than assuming the exact default URL shape, inspect the generated _site tree after a build to confirm the paths.
Front Matter Defaults and Scoping
If every staff document shares a layout or an author, you can set defaults in _config.yml scoped by path and type. The type value should match your collection name, but confirm this in your Jekyll version because expectations have shifted across major releases.
Verification Before You Commit
Start small. Add one collection entry and one document, then run bundle exec jekyll serve from your project root. Create a temporary page containing {{ site.staff | inspect }} and {{ site.staff.size }} to confirm the documents are visible and to inspect their attributes before writing final templates.
Toggle output off and on, rebuild, and compare the _site tree each time. Run bundle exec jekyll build --verbose and check the console for warnings about missing layouts or permalink problems. Pin your Jekyll version in a Gemfile and run bundle exec jekyll --version so your local results are reproducible.
Limitations Worth Planning Around
Collections are not a universal replacement for posts or data files. If you have thousands of records and do not need individual pages, a non-output collection or a file in _data/ will build faster. Permalink placeholders and defaults have changed across Jekyll lines, so verify the generated URLs rather than relying on remembered patterns. Finally, hosted builds that pin an older Jekyll or restrict plugins may not reproduce local output that depends on newer behavior or extra gems.
Actionable Next Step
Pick one content type that is currently squeezed into posts or data files. Declare it as a collection with output: false first, render it in Liquid, and confirm the data looks right. Only then enable output: true and check the resulting _site paths. If the build slows or URLs look wrong, you have lost nothing but a rebuild.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.