List blog posts grouped by year with Eleventy

A common request I run into is displaying blog posts grouped by publishing year as I have done in my own blog listing. Here’s how to do it with the static site generator Eleventy. I have published the code as a demo project where you can see how to do both yearly and monthly.
Create a new collection in Eleventy config file
Eleventy project is configured with
a configuration file
that
can have different names. I’ll refer to this file as
eleventy.config.js
but you can adjust
accordingly with your own project.
Inside this file, you should have a function like
// ESM
export default async function(eleventyConfig) {
// Configure Eleventy
};
// or in CommonJS
module.exports = async function(eleventyConfig) {
// Configure Eleventy
};
Inside this function, we’ll create a new collection that will contain our yearly grouping:
export default async function(eleventyConfig) {
// Create a new collection that can be accessed in templates
eleventyConfig.addCollection('byYear', collection => {
// Find all Markdown posts in blog folder.
// You need to adjust this glob pattern to match your
// folder structure.
const allPosts = collection.getFilteredByGlob('blog/*.md');
// Create an empty object for our new collection
const byYear = {};
// Go through all original posts
allPosts.forEach(post => {
// Assuming there's a date field in front matter for each post,
// extract the year for the post
const date = new Date(post.data.date);
const year = date.getFullYear();
// If current year has not been encoutered yet,
// create a new entry.
if(!byYear[year]) {
byYear[year] = [];
}
// Add post to the corresponding year
byYear[year].push(post);
});
// Our posts are now grouped by year so
// let's return it as the new collection
return byYear;
});
// The rest of your configuration file...
}
Eleventy is very flexible in how you structure your project and identify your
blog posts. In the above example, I’m using
collections.getFilteredByGlob
which allows you to find all files that match the pattern - in this case, all
Markdown files from folder blog/
.
Another option is to use
collections.getFilteredByTag
which allows you to find all content from your Eleventy project based on a
tag. For example, if you tag your blog posts as “blog-post”, you can find them
all with
collections.getFilteredByTag('blog-post')
.
Display on blog list
We can now display these posts in a blog listing page, let’s call it
blog.njk
{% for year, posts in collections.byYear|dictsort|reverse %}
<h2>{{ year }}</h2>
<ul>
{% for post in posts | reverse %}
<li><a href="{{post.url}}">{{post.data.title}}</a></li>
{% endfor %}
</ul>
{% endfor %}
I’m using
dictsort
filter to sort the byYear
object by key
and then
reverse
it to put the newest on the top. I’m using reverse again for all the posts to
list them in reverse chronological order too.
Add monthly grouping
If you blog multiple times a month, you may want to add more granularity and
list posts by month as well. To do that, let’s adjust the collection creation
function as (in eleventy.config.js
):
eleventyConfig.addCollection('byMonth', collection => {
// Find all Markdown posts in blog folder.
// You need to adjust this glob pattern to match your
// folder structure.
const allPosts = collection.getFilteredByGlob('blog/*.md');
// Create an empty object for our new collection
const grouped = {};
// Go through all original posts
allPosts.forEach(post => {
// Assuming there's a date field in front matter for each post,
// extract the year for the post
const date = new Date(post.data.date);
const year = date.getFullYear();
const month = date.getMonth();
// If current year has not been encoutered yet,
// create a new entry.
if(!grouped[year]) { grouped[year] = {}; }
if(!grouped[year][month]) { grouped[year][month] = []; }
// Add post to the corresponding year
grouped[year][month].push(post);
});
// Our posts are now grouped by year so
// let's return it as the new collection
return grouped;
});
// I also like to add a filter to turn month numbers into short headings
eleventyConfig.addFilter('toMonth', month => {
return ['Jan', 'Feb', 'Mar', 'Apr',
'May', 'Jun', 'Jul', 'Aug',
'Sep', 'Oct', 'Nov', 'Dec'][month]
});
And then in the template:
{% for year, months in collections.byMonth|dictsort|reverse %}
<h2>{{ year }}</h2>
{% for month, posts in months|dictsort|reverse %}
<h3>{{ month|toMonth }}</h3>
<ul>
{% for post in posts | reverse %}
<li><a href="{{post.url}}">{{post.data.title}}</a></li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
If something above resonated with you, let's start a discussion about it! Email me at juhamattisantala at gmail dot com and share your thoughts. In 2025, I want to have more deeper discussions with people from around the world and I'd love if you'd be part of that.