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.