Skip to content

Content that fills itself in

Sorting and filtering

Copy page

Four filters do almost everything, and one mistake accounts for almost every list that comes out wrong.

The mistake

Filters on the for line are ignored. This looks completely reasonable and does not do what it says:

{% for page in site.pages | where: "parent", "services" %}
  {{ page.title }}
{% endfor %}

That loops over every page on the site, not the ones inside Services. The where is silently dropped — the page renders, nothing errors, and the list is wrong in a way that looks like a bug in the product.

Build the list first, then loop it:

{% assign items = site.pages | where: "parent", "services" %}
{% for item in items %}
  {{ item.title }}
{% endfor %}

You will not ship this by accident: publishing refuses the first version and tells you to assign first. But it is worth knowing why, because it is the one place the language reads as though it means something it does not.

Two lists

site.pages is your pages and collections.articles is your articles. They are separate, and neither ever contains the other — see Articles.

Which one you start from decides whether you need a filter at all. Articles are already just the articles, in the order you arrange them, so a blog index has nothing to filter by and sorts by date; pages are every page on the site, so listing one section means saying which.

where

Keeps the items whose field matches:

{% assign items = site.pages | where: "parent", "services" %}

parent is the parent’s address, not its title — "services" for pages inside /services. Top-level pages have "".

A collection’s items have a parent too, and it is never a page. In a flat collection, such as Articles, every item’s parent is "". In a hierarchical one it is the path of the item it sits under. See Collections.

where on a tag

tags is a list rather than a single value, and where handles that: it keeps every item carrying the tag you name.

{% assign recipes = collections.articles | where: "tags", "recipes" %}

It matches exactly, so "Recipes" against an article tagged recipes is an empty list and nothing else. See Tags.

where always needs a value

Both arguments, every time — the field and what it has to be.

{% assign withPictures = site.pages | where: "image" %}

That does not mean “the pages that have a picture”, and publishing refuses it rather than letting it look like it does. To keep the items that have one, test inside the loop instead:

{% for item in site.pages %}
  {% if item.image %}<img src="{{ item.image.src }}" alt="{{ item.image.alt }}">{% endif %}
{% endfor %}

sort and reverse

sort takes the field to sort by, and goes smallest first:

{% assign posts = collections.articles | sort: "date" | reverse %}

Dates sort oldest first, so newest-first is sort then reverse. For a hand- ordered list — services, team members — sort by order, which is the position you set by dragging the pages around, and do not reverse it. Items have order as well, but a collection already arrives in the order you dragged its items into, so it only needs a sort when you want something else, such as dates.

limit

Goes on the for, and is the one thing that does:

{% assign posts = collections.articles %}
{% for post in posts limit: 3 %}
  {{ post.title }}
{% endfor %}

Chaining

They read left to right, and the order matters: filter, then sort, then reverse.

{% assign recent = site.pages | where: "parent", "services" | sort: "order" %}

Empty is not an error

A where that matches nothing gives an empty list and the loop runs zero times. That is why the {% else %} arm is worth writing — it is the difference between “no articles yet” and a heading with nothing under it.

On this page