12.1 The Lookup Order Algorithm: Type, Layout, Kind, Output Format
Alright, let’s pull back the curtain on Hugo’s template lookup order. This is where the magic happens—or where it fails spectacularly, leaving you staring at a blank page wondering if you’ve angered the digital gods. I’m here to make sure it’s the former.
Think of Hugo not as a meticulous librarian, but as a brilliant, slightly impatient friend who’s helping you find a book. They have a very specific, logical search pattern they run through, and if they don’t find the exact match straight away, they start generalizing until they do find something. They always start with the most specific template possible for the content you’re trying to render. If that doesn’t exist, they fall back to something more generic. This process is called the “lookup order,” and understanding it is the key to unlocking (or debugging) your entire site.
The algorithm is a path, built from a combination of variables: the content’s type (like “post” or “project”), its kind (is it a single page, the home page, a list of pages?), its layout (a front matter override), and the output format (HTML, JSON, RSS, etc.). Hugo constructs a potential template filename and checks if it exists. If not, it moves to the next, less specific option.
The Base Lookup Paths
Hugo constructs its lookup paths in the /layouts directory following this pattern. Let’s break down the variables:
- Type: This typically comes from the directory the content lives in (
content/posts/has a type ofposts). It can be overridden in front matter withtype: somethingelse. - Kind: A Hugo-internal classification of content. The main ones you’ll care about are
page(for a single piece of content),home(for the root homepage),section(a list of pages within a section, likeposts),taxonomy(a list of terms, e.g., all tags), andterm(a list of content for a specific term, e.g., all posts tagged “javascript”). - Layout: A front matter field (
layout: special) that lets you request a specific template for one piece of content. Don’t overuse this; it’s a code smell. - Output Format: Defined in your config, this is usually
htmlbut could bejson,rss, or a custom one.
The Algorithm in Practice: A Single Blog Post
Let’s make this concrete. You have a file at content/posts/my-cool-article.md. By default, its type is posts and its kind is page. You’re rendering it as HTML. Hugo will look for these files, in this exact order, and use the first one it finds:
layouts/posts/single.html<- Most specific: type + kindlayouts/posts/single.html<- Wait, didn’t we just…? Oh, output format. Right. Forhtml, it’s implied, so this is the same as #1. For a custom output format likeamp, it would look forlayouts/posts/single.amp.html.layouts/_default/single.html<- Fall back to the default type: kindlayouts/_default/single.html<- Again, output format implied.
It will not look for layouts/posts/page.html or other permutations. The kind for a single page is always single. The term “page” is confusingly used for both the content and the kind. I didn’t name it, don’t look at me.
Here’s the kicker: if you only have a layouts/_default/single.html, every single page on your site—posts, projects, anything—will use it. This is either fantastic for consistency or a nightmare if you need variety. Your choice.
The Homepage is a Special Snowflake
The root homepage (/) is unique. Its type is empty and its kind is home. Its lookup order is special and worth memorizing to avoid head-scratching later:
layouts/index.htmllayouts/home.htmllayouts/_default/list.html<- Yes, the homepage is technically a list of the pages in the root ofcontent/layouts/_default/home.html
Notice that layouts/index.html is the first choice. This is the designer’s questionable choice I called out earlier. It’s a historical artifact from the old days of web development where index.html was the default file served. It’s inconsistent with the rest of the pattern, and we all just have to live with it. So, for your homepage, you almost always want layouts/index.html.
Lists, Sections, and Taxonomies (Oh My!)
For a section list (e.g., a list of all posts at /posts/), the type is posts and the kind is section. The lookup order is:
layouts/posts/section.htmllayouts/posts/list.html<- A common pitfall! People often createlist.htmlfor a section and wonder why it’s not working.section.htmlis checked first.layouts/_default/section.htmllayouts/_default/list.html
The same logic applies to taxonomy and term lists. For all posts tagged “tech” (/tags/tech/), kind is term, type is tags. Hugo looks for layouts/tags/term.html, then layouts/tags/list.html, then the _default versions.
Best Practices and Pitfalls
- Start with
_default/: Begin your theme by creating_default/baseof.html,_default/single.html, and_default/list.html. This catches everything. Then, create more specific templates (likeposts/single.html) only when you need to break from the default. This is the “progressive enhancement” of Hugo templating. - Debug with
hugo --verbose: When a template isn’t being used, run Hugo with the--verboseflag. It will literally print out the lookup path it’s following in your terminal. This is your number one debugging tool. It’s like getting the answers to the test. list.htmlvs.section.html: Remember the order. If you want a template for a specific section (e.g.,posts), you usually wantlayouts/posts/section.html, notlist.html. Thelist.htmlin a type directory is a fallback for that type, not the primary target.- Output Formats Matter: If you’re building a JSON index or an AMP version of your site, remember the filename pattern:
single.json.html,list.amp.html, etc. The lookup order fully applies to these as well.