Flexible tag-style functionality for custom keys in Eleventy

Direct Link

I have an open-source, Eleventy-based project where the posts are restaurants, each of which is located in a particular city, and contributors to the repo can add a new restaurant as a simple markdown file.

I just had to solve a conundrum wherein I wanted a custom front matter key, city, to have similar features as tags, namely:

  1. it takes arbitrary values (e.g. Glasgow, or London, or Cañon City, or anything a contributor might choose);
  2. there is a corresponding cityList collection;
  3. there is a page which lists all cities in the cityList collection as links; and
  4. there’s a page for each city which lists all restaurants in that city (much like a “Tagged Glasgow” page).

You could be forgiven for asking: why didn’t I just implement the cities as tags? I could have tagged posts with “glasgow”, or “edinburgh” for example. Well, here was my rationale:

  • for cities, I need the proper, correctly spelled, spaced and punctuated name so I can display it as a page title. A lowercased, squashed together “tag” version wouldn’t cut it;
  • I list “all tags” elsewhere and wouldn’t want the cities amongst them. Although Eleventy allows you to filter given tag values out, in this case that would be a pain to achieve because the city values are not known up front;
  • Lastly it just felt right for ease of future data manipulation that city should be a separate entity.

This task was a bit of a head-scratcher and sent me down a few blind alleys at first. Rightly or wrongly, it took me a while to realise that only all posts for tag values are automatically available as collections in Eleventy. So any other collections you need, you have to DIY. Once I worked that out, I arrived at a strategy of:

implement all the requisite functionalty on tags first, then emulate that functionality for my custom key.

First port of call was the Eleventy Tag Pages tutorial. That showed me how to use the existing “collection for each tag value” to create a page for each tag value – the “All posts tagged with X” convention. (Here’s an example on this site.)

I then referenced the eleventy-base-blog repo which takes things further by creating a page which lists “all tags”. To achieve this you first need to create a custom tagList collection, then you create a page which accesses that new collection using collections.tagList, iterates it and displays each tag as a link. Each tag link points to its corresponding “All posts tagged with X” page we created in the step above.

So now I had everything working for tags. Next step: emulate that for cities.

Here’s what I ended up doing:

Create new file _11ty/getCityList.js

module.exports = function(collection) {
  let citySet = new Set();
  collection.getAll().forEach(function(item) {
    if( "city" in item.data ) {
      let city = item.data.city;  
      citySet.add(city);
    }
  });

  return [...citySet];
};

Then add the following to .eleventy.js

// Create a collection of cities
eleventyConfig.addCollection("cityList", require("./_11ty/getCityList"));

// Create "restaurants in city" collections keyed by city name
eleventyConfig.addCollection("cityCollections", function(collection) {
  let resultArrays = {};
  collection.getAll().forEach(function(item) {
    if(item.data["title"] && item.data["city"]) {
      if( !resultArrays[item.data["city"]] ) {
        resultArrays[item.data["city"]] = [];
      }
      resultArrays[item.data["city"]].push(item);
    }
  });
  return resultArrays;
});

Next, create new file cities-list.njk:

---
permalink: /cities/
layout: layouts/home.njk
---
<h1>All Cities</h1>

<ul>
{%- for city in collections.cityList -%}
  {% set cityUrl -%}/cities/{{ city }}/{% endset %}
  <li><a href="{{ cityUrl | url }}">{{ city }}</a></li>
{%- endfor -%}
</ul>

Finally, create new file posts-in-city.njk:

---
renderData:
  title: Restaurants in “{{ city }}”
pagination:
  data: collections.cityList
  size: 1
  alias: city
permalink: /cities/{{ city | slug }}/
---
<h1>Restaurants in {{ city }}</h1>

{% set postslist = collections.cityCollections[ city ] %}
{% include 'postslist.njk' %}

And that’s a wrap! Eleventy will do the rest when it next runs, creating for each city a page which lists all restaurants in that city.

Footnote: I should acknowledge this 11ty Github issue in which Ed Horsford was trying to do something similar (create a separate tags network) leading to Zach Leatherman pitching in with how he created noteTags for his website’s Notes section. That led me to Zach’s website’s repo on Github, specifically .eleventy.js and tag-pages.njk, without which I wouldn’t have found my way.

External Link Search