---
title: "Adding categories to Eleventy"
date: 2022-05-28T00:00:00
slug: adding-categories-to-eleventy

---
I've decided to put a bit more love into this blog, I've neglected it since the new year. As part of that, I wanted to make a few more changes - namely, I wanted some better navigation to allow me to write different types of content. So, I've added a little category list to the site to allow people to search by different tags.

## Organising posts

First of all, to identify blog posts, I use a single tag: `posts`. I use an `11tydata.json` file in my posts directory that looks like this to ensure every post is automatically tagged correctly:

```javascript
{
  "tags": [
    "posts"
  ],
  "permalink": "post/{{ title | slug }}/",
  "layout": "post.njk"
}
```

I also have a `draft` tag, that I use to un-publish posts that I'm working on without needing to keep WIP on one machine. I'll assume that any other tag that an item in the `posts` collection has is it's category, and that a post can have multiple categories.


## Getting the category list

So, to generate a list of categories and the number of posts in each category, I've added a [simple custom collection](https://www.11ty.dev/docs/collections/#collection-api-methods) to my site, called `categories`. Here's the code:

```javascript
eleventyConfig.addCollection('categories', (collectionApi) => {
        const posts = collectionApi
            .getFilteredByTag("posts")
            .filter(p => !p.data.tags.includes("draft"));

        return posts.reduce((tags, post) => {
            post.data.tags.filter(tag => tag !== 'posts').forEach(tag => {
                if (!tags[tag]) {
                    tags[tag] = 0;
                }
                tags[tag]++;
            });
            return tags;
        }, {"All posts": posts.length})
    });
```

It's fairly simple, even if Javascript's `reduce` is a callback-headache. All we're doing is getting all of the items in the `posts` collection, removing anything tagged as a draft post, and then for each tag we're first checking if the tag already exists. If it doesn't exist, we initialise it in our tags object with a count of 0. Then, we increment the tag count by 1. We then also add an extra tag called `All posts`, which is the total count of the `posts` object.

The output of this function is an object that looks like this:

```javascript
{
    "All posts": 10,
    "frontend": 3,
    "backend": 2,
    "recipes": 4,
    "books": 1
}
```

## Displaying categories

Listing the categories is easy, we just need to use our new collection:

{% raw %}
```html
<ul>
{% for category, count in (collections.categories) %}
    {% if category == "All posts" %}
        <li><a href="{{ '/blog' | url }}">{{ category }} ({{ count }})</a></li>
    {% else %}
        {% set caturl = ["/blog", "category", category] | join("/") %}
        <li><a href="{{ caturl | url }}">{{ category }} ({{ count }})</a></li>
    {% endif %}
{% endfor %}
</ul>
```
{% endraw %}


To actually display a category, [Eleventy has an easy guide for this](https://www.11ty.dev/docs/quicktips/tag-pages/). We just need a bit of customisation to use our blog layout, filter out tags such as drafts, and the categories themselves, and then set our permalink:

{% raw %}
```yaml
---
pagination:
  data: collections
  size: 1
  alias: tag
  filter:
    - draft
    - categories
    - all
permalink: /blog/category/{{ tag }}/
layout: blog.njk
eleventyComputed:
  pageTitle: Posts Tagged "{{ tag }}"
title: Lewis Dale's Blog
---

{% set taglist = collections[ tag ] %}
{% for post in (taglist | filterDrafts | sortedByDate) %}
  {% include "components/blogpost.njk" %}
{% endfor %}
```
{% endraw %}

And that's pretty much it! There's probably still some work to be done with paginating the tags once I have enough posts to need it.
