I ran into this problem when I was creating a tag filter for my blog: I needed a list of all the tags I used in my posts, but I wanted to filter out any duplicates. I also wanted tags from only published posts, so I needed to use the where filter. Below is how I achieved this result:
Example post front matter:
title: Get a List of Unique Items in Jekyll
published: true
tags:
- jekyll
Example loop:
{% assign tags = site.posts | where: 'published', 'true' | map: 'tags' | join: ',' | split: ',' | uniq %}
{% for tag in tags %}
{{ tag }}
{% endfor %}
Here’s what each line does:
I defined a variable for the array I’m creating so I could effectively add all the filters:
{% assign tags = site.posts %}
I added the where
filter to get only the posts that are published:
{% assign posts = site.posts | where: 'published', 'true' %}
The where
operates based on an object/value pair. In the above example, I’m telling jekyll to get all posts where the published object has a value of true, e.g. published: true
. where
is just a filter for your frontmatter.
I added the map
filter in order to create an array with only the tag filters:
{% assign tags = site.posts | where: 'published', 'true' | map: 'tags' %}
map
is a useful, but poorly documented method like much of anything relating to arrays. I had trouble with this part, so I will explain it for you: map
tells the loop to create a new array with only one of the objects. In the example above, I created an array with all the posts in my site: site.posts
, then I made a new array with only the tags
object from each post. Now I have an array of just the tags, after adding map: 'tags'
.
The raw output of the tags array would look like this: [["jekyll"]]
so I cleaned it up with join
and created a comma separated string that I can create an array that jekyll will recognize:
{% assign tags = site.posts | where: 'published', 'true' | map: 'tags' | join: ',' %}
join
creates a comma separated string of values, e.g. tag one, tag two, tag three. With join
, I now have a string that jekyll can make an array with.
split
creates the array from which I can use the uniq
filter to remove any duplicate values:
I’m telling jekyll to parse objects into an array based on the comma separator in the line above: split: ','
now with the array created, I can use the uniq
filter that will only work on arrays:
{% assign tags = site.posts | where: 'published', 'true' | map: 'tags' | join: ',' | split: ',' | uniq %}
Woot! Our tags are unique and ready to be looped through:
{% for tag in tags %}
{{ tag }}
{% endfor %}
tags
is our array and we want to get each tag
from the list of tags in each object.
The other method
I discovered another method from tooling around. In this method I create an array, filter the array for unique values, then loop through that array to get my tags. Basically the same as the method above, but more complicated! I just comment above each line here for a more brief explanation:
Create an empty array to add objects to:
{% assign tagArray = '' | split: ',' %}
Loop through the posts:
{% for post in site.posts %}
Get only published posts:
{% if post.published != "false" %}
Loop through the tag lists within each post:
{% for tag in post.tags %}
Push each tag to the tagArray. The array has to be defined as a variable again so it can be overwritten with the new objects:
{% assign tagArray = tagArray | push: tag %}
{% endfor %}
{% endif %}
{% endfor %}
Create a new array from the filtered array:
{% assign uniqTags = tagArray | uniq %}
Loop through your filtered array to get unique tags:
{% for tag in uniqTags %}
{{ tag }}
{% endfor %}