Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

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.