Right, so you’ve found a Hugo theme you love, but its templates are just… wrong. Maybe the single.html template has some weird sidebar you don’t want, or the list.html is missing a crucial piece of metadata. Your first instinct might be to fork the theme’s repository and start hacking away. Don’t. That’s the path to becoming a full-time, unpaid maintenance developer for a fork you’ll never be able to update.

Hugo’s template lookup order is your brilliant escape hatch. It’s the rulebook for how Hugo’s template engine goes hunting for the right file to render your content. The core principle is beautiful in its simplicity: your project’s layouts directory takes absolute, total precedence over anything in the themes directory. This isn’t a suggestion; it’s a dictatorship. You are the boss of your layouts folder.

This means you don’t need to copy the entire theme’s layouts structure into your project. You only need to create the specific file you want to override. Hugo will find your version first and use it, blissfully ignoring the theme’s original. It’s the software equivalent of saying, “I got this,” and your theme just shrugs and goes back to drinking coffee.

The Lookup Order: A Step-by-Step Hunt

Let’s get into the nitty-gritty. When Hugo renders a piece of content, it goes on a very specific scavenger hunt. For a regular page (a “single” template), the lookup order is:

  1. /layouts/_default/single.html
  2. /layouts/<SECTION>/single.html
  3. /themes/<THEME>/layouts/_default/single.html
  4. /themes/<THEME>/layouts/<SECTION>/single.html

It stops at the first file it finds. So, if you create layouts/_default/single.html, Hugo uses it and never even peeks at steps 3 or 4. This is your most powerful tool.

Let’s say your theme, my-fancy-theme, has a section list template at themes/my-fancy-theme/layouts/news/list.html that you find lacking. You want to override it. You don’t touch the theme. You create this file in your own project:

# Create the necessary directories and file
mkdir -p layouts/news
touch layouts/news/list.html

Now, you can put whatever you want in that list.html file. You can write it from scratch, or, more sensibly, you can use the theme’s original as a starting point. This is where Hugo’s greatest party trick comes in: partials.

The Golden Rule: Steal the Theme’s Partials

A well-built Hugo theme structures its templates using partials. This is your key to surgical overrides. Instead of forking the entire theme, you override one template file and then lean heavily on the theme’s own partials for the parts you don’t want to change.

Let’s make this concrete. Imagine the theme’s original news/list.html might look something like this:

{{ define "main" }}
<article>
    <header>
        <h1>{{ .Title }}</h1>
    </header>
    <div class="content">
        <!-- The actual list content -->
        {{ .Content }}
        {{ range .Paginator.Pages }}
            <h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
        {{ end }}
    </div>
    <aside class="sidebar">
        {{ partial "news/sidebar" . }} <!-- Pulling in a partial -->
    </aside>
</article>
{{ end }}

You hate that sidebar. It’s absurd. Who puts a calendar widget on a news listing? To remove it, your override file at layouts/news/list.html would be:

{{ define "main" }}
<article>
    <header>
        <h1>{{ .Title }}</h1>
        <!-- I'm adding a subtitle the theme didn't have! -->
        <p class="subtitle">All the news that's fit to print (and some that isn't)</p>
    </header>
    <div class="content">
        <!-- I still want the theme's main content, so I just... use it -->
        {{ .Content }}
        {{ range .Paginator.Pages }}
            <h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
        {{ end }}
    </div>
    <!-- And I simply... delete the entire aside tag. Gone. -->
</article>
{{ end }}

See what happened? I kept the parts of the theme’s structure I liked, added something new, and removed the part I hated. I didn’t have to rewrite the pagination logic. I didn’t have to fork anything. I made a targeted, precise change.

Common Pitfalls and the “WTF?” Moments

  1. Caching Heartbreak: The number one “it’s not working!” complaint is due to Hugo’s aggressive caching. You just created layouts/news/list.html, ran hugo server, and see no change? Kill the server and restart it. The watch function is brilliant, but it can sometimes miss new files being added. A restart always fixes it.

  2. The Specificity Trap: Remember the lookup order. If you have a section called news, Hugo will use layouts/news/list.html over layouts/_default/list.html. This is usually what you want. But if you create an override for one section and then wonder why your changes aren’t applying to another section (blog, for example), this is why. You’ve become too specific. The _default directory is your friend for site-wide changes.

  3. Theming Inheritance is Weird: If you’re using multiple themes (yes, Hugo supports that, for some reason), the lookup order goes through each theme directory in reverse order. The last theme listed in your config is checked first. It’s a niche feature that mostly exists to let themes “extend” others, but it can cause head-scratching if you’re not expecting it. For 99% of you, just worry about your main theme.

The takeaway is this: you hold all the power. The theme is a suggestion, a starting point. With Hugo’s lookup order, you can override as much or as little as you want, all while keeping your project decoupled from the theme’s source. This lets you update the theme easily later without having to merge your customizations back in. It’s not just a feature; it’s the entire philosophy of how Hugo expects you to work. So go on, be the boss.