Different ways to use the range method in Hugo
The range method in combination with where or sort is extremely powerful in Hugo - static website engine. Learn how to use range for pages, taxonomy or even numbers.
Author: Markus A. Wolf
Updated: February 2024
The range method changes the scope - so you have to add $ to a variable if you need a global scope.

Loop through pages

Let’s start with something easy. The following example loops through every page in content ordered by date with the newest first.

<div class="my-12">
    {{ range .Site.RegularPages }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ .RelPermalink }}
        </a>
    {{ end }}
</div>

The next example is a loop through all pages that are inside the section (folder) /blog and if there are no articles inside the blog, it shows “No articles found!”.

<div class="my-12">
    {{ range where .Site.RegularPages "Section" "blog" }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ else }}
        <p>
            No articles found!
        </p>
    {{ end }}
</div>

Build is sort methods

To change the order you can use the built-in methods before making it more complicated with the sort method. To change the order you can use the .Reverse method. The built-in sorting methods are helpful in most situations.

<div class="my-12">
    {{ range (where .Site.RegularPages "Section" "blog").ByTitle }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

In some situations it is very useful to get all articles without the one you are reading right now ordered by date. So the following range shows all articles except the one you’re reading right now.

<div class="my-12">
    {{ range where (where .Site.RegularPages "Section" "blog") "Permalink" "!=" .Page.Permalink }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div> 

To make it even more useful you can add the first method and show only the latest 3 articles.

<div class="my-12">
    {{ range first 3 (where (where .Site.RegularPages "Section" "blog") "Permalink" "!=" .Page.Permalink) }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

Difference between .Site.RegularPages and .Site.Pages

With .RegularPages you get all pages that are real pages for the actual language and no sections or taxonomy. If you use .Pages you get every page for the actual language and this includes sections and taxonomy.

The difference to .AllPages is that you get every page but it doesn’t consider if the page is available in the actual language.

Pages with multiple categories or tags

There are some use cases where you might need articles from different taxonomies combined or separated. The first example shows all articles that are having both tags.

<div class="my-12">
    {{ $articles := where (where .Site.RegularPages "Section" "blog") "Permalink" "!=" .Page.Permalink }}
    {{ range $articles.ByParam "some_param_you_like" }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

Would work too.

<div class="my-12">
    {{ $articles := where (where .Site.RegularPages "Section" "blog") "Permalink" "!=" .Page.Permalink }}
    {{ range sort $articles ".Params.some_param_you_like" "asc" }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

The second example shows how to get the first 3 articles that contain two tags.

<div class="my-12">
    {{ $articles := where (where .Site.RegularPages "Section" "blog") "Permalink" "!=" .Page.Permalink }}
    {{ range first 3 (where $articles "Params.tag" "intersect" (slice "tag1" "tag2")) }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

Range through taxonomy

Looping through Hugo’s taxonomy can be confusing sometimes so here are two ways how to range through it and show articles and terms on the page.

<div class="my-12">
    {{ range $index, $taxonomy := .Site.Taxonomies }}
        <h2 class="text-2xl">
            Taxonomy: {{ $index }}
        </h2>
        {{ range $key, $value := $taxonomy }}
            <div class="mb-4">
                <h3 class="text-xl">Term: {{ $key }}</h3>
                {{ range $value.Pages }}
                    <a href="{{ .RelPermalink }}" class="block">
                        {{ .Title }} / {{ dateFormat "January 2006" .Date }}
                    </a>
                {{ end }}
            </div>
        {{ end }}
    {{ end }}
</div> 
<div class="my-12">
    {{ range $key, $value := .Site.Taxonomies.categories }}
        <div class="mb-4">
            <h3 class="text-xl">Term: {{ $key }}</h3>
            {{ range $value.Pages }}
                <a href="{{ .RelPermalink }}" class="block">
                    {{ .Title }} / {{ dateFormat "January 2006" .Date }}
                </a>
            {{ end }}
        </div>
    {{ end }}
</div> 

The second example only loops through a specific taxonomy like tags or categories.

The last example loops through all articles with a specific term inside a taxonomy. There are two ways to get access to the pages - both are working fine but only one can be used in all situations.

<div class="my-12">
    {{ $articles := slice }}
    {{ range slice "tag1" "tag2"}}
        {{ range index $.Site.Taxonomies.tags . }}
            {{ $articles = $articles | append .Page }}
        {{ end }}
    {{ end }}
    {{ range $articles }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div> 
<div class="my-12">
    {{ range $key, $value := .Site.Taxonomies.categories.tag }}
        <a href="{{ .RelPermalink }}" class="block">
            {{ .Title }} / {{ dateFormat "January 2006" .Date }}
        </a>
    {{ end }}
</div>

This is NOT working

<div class="my-12">
    {{ range $key, $value := .Site.Taxonomies.categories.tag-name }}
    {{ end }}
</div>

Use this approach instead.

<div class="my-12">
    {{ range $key, $value := index .Site.Taxonomies.topics "tag-name" }}
    {{ end }}
</div>

Range - other areas of application

With the range method you can do a lot more things like looping through numbers like a foo loop or using is like a for each loop. You can even split a string and loop through every word.

<div class="my-12">
    {{ range $number :=  (seq 10) }}
        <p class="text-xl">
            {{ $number }}
        </p>
    {{ end }}
</div>
<div class="my-12">
    {{ range $number :=  (slice "one" "two" "three" "four" "five") }}
        <p class="text-xl">
            {{ $number }}
        </p>
    {{ end }}
</div> 
<div class="my-12">
    {{ range $number :=  split "Lorem ipsum dolor sit amet consectetur adipisicing elit. Id, alias." " " }}
        <p class="text-xl">
            {{ $number }}
        </p>
    {{ end }}
</div>

Range with continue - Different approaches

There are a lot of cases where it might be handy if you could use something like break or continue in Hugo. And this topic does have a long history in the Hugo community. It was implemented and after some issues, removed in 2018. If you are interested in the details: Revert CL 66410 “add break, continue actions in ranges” .

The solution is not the nicest but it works.

<div class="my-12">
    {{ range .Site.RegularPages }}
        {{ if eq .RelPermalink $.Page.RelPermalink }}
            <!-- Do nothing -->
        {{ else }}
            <a href="{{ .RelPermalink }}" class="block">
                {{ .Title }} / {{ .RelPermalink }}
            </a>
        {{ end }}
    {{ end }}
</div>
<div class="my-12">
    {{ range .Site.RegularPages }}
        {{ if not (eq .RelPermalink $.Page.RelPermalink) }}
            <a href="{{ .RelPermalink }}" class="block">
                {{ .Title }} / {{ .RelPermalink }}
            </a>
        {{ else }}
            <!-- Do nothing -->
        {{ end }}
    {{ end }}
</div>

If you have a better workaround please send me a DM @markusantonwolf - I would love to hear about it.

All links in a practical list

More articles

Thoughts, topics or just solutions I would like to make available to you, colleagues and fellow enthusiasts.