# How Hugo's Menu System Works: Defining Menus in Front Matter vs Site Configuration

> Learn how Hugo's menu system works and define navigation menus in front matter or site config for flexible site structure and template rendering.

- Repository: [GoHugo.io/hugo](https://github.com/gohugoio/hugo)
- Tags: internals
- Published: 2026-02-28

---

**Hugo's menu system assembles a hierarchical navigation tree at build time by merging menu entries defined in site configuration files, page front matter, and automatic section pages, exposing the final structure through `site.Menus` for template rendering.**

Hugo's menu system is a core component of the `gohugoio/hugo` static site generator, responsible for building hierarchical navigation structures at build time. It aggregates menu entries from multiple sources—site configuration, individual page front matter, and automatic section pages—into a unified tree exposed to templates via `site.Menus`.

## The Three Sources of Menu Entries

Hugo collects raw menu entries from three distinct sources before assembling the final hierarchy.

### Site Configuration

The static `menus` block in your site configuration ([`config.toml`](https://github.com/gohugoio/hugo/blob/main/config.toml), [`config.yaml`](https://github.com/gohugoio/hugo/blob/main/config.yaml), or [`config.json`](https://github.com/gohugoio/hugo/blob/main/config.json)) defines global menu entries independent of any specific content page. These entries typically define permanent navigation items like "Home" or "About" that may not correspond to individual content files.

### Page Front Matter

Individual content pages can declare their membership in one or more menus through the `menus` (or legacy `menu`) key in front matter. This method connects specific content to navigation structures, allowing dynamic menu generation based on your content hierarchy.

### Automatic Section Menus

When `sectionPagesMenu` is set in site configuration (e.g., `sectionPagesMenu = "main"`), Hugo automatically creates menu entries for every top-level section (like `/blog/` or `/docs/`). This occurs in `site.assembleMenus()` without requiring manual entry definition.

## How Hugo Assembles the Menu Hierarchy

The assembly process occurs in [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go) within the `assembleMenus()` method (lines 1398–1475). This function implements a three-phase pipeline:

1. **Flattening and enrichment**: Hugo iterates over all three sources (config, pages, sections) and creates a temporary flat map keyed by **menu name + entry identifier**. For each entry, it:
   - Resolves `pageRef` to an actual `Page` object using `s.getPage`
   - Generates fully-qualified URLs when no page reference exists via `s.createNodeMenuEntryURL`
   - Attaches page metadata (title, URL, etc.) through `navigation.SetPageValues`

2. **Parent-child resolution**: Hugo detects hierarchical relationships via the `parent` field in each menu entry. Entries specify their parent using the parent's `identifier` value.

3. **Tree construction**: A second pass builds the `children` map and populates the top-level `navigation.Menus` structure—a `map[string]navigation.Menu` exposed to templates as `site.Menus`.

## Defining Menus in Front Matter vs Configuration

### Site Configuration Method

Define static menu entries in your configuration file for items that don't correspond to specific content pages:

```toml
[menus]

  [[menus.main]]
    identifier = "home"
    name       = "Home"
    url        = "/"
    weight     = 1

  [[menus.main]]
    identifier = "about"
    name       = "About"
    url        = "/about/"
    weight     = 2
    [menus.main.params]
      class = "highlight"

```

### Front Matter Method

Add pages to menus directly in content files. The [`navigation/pagemenus.go`](https://github.com/gohugoio/hugo/blob/main/navigation/pagemenus.go) file (lines 44–97) handles three possible front matter shapes:

**Single menu assignment:**

```yaml
title: "Contact"
menus: "main"

```

**Multiple menu assignment:**

```yaml
title: "Blog"
menus:
  - "main"
  - "footer"

```

**Structured configuration with hierarchy:**

```toml
title = "Products"
[menus.main]
  parent = "products"
  weight = 20
  pre    = "<i class='fa fa-box'></i>"
  [menus.main.params]
    class = "nav-product"

```

The structured format allows specifying `parent` (using the parent's identifier), `weight` for ordering, and `pre`/`post` for HTML markup surrounding the link.

### When to Use Which

- **Use site configuration** for static navigation items (Home, About, Contact) that may not have dedicated content pages or when you need precise control over URLs independent of content structure.
- **Use front matter** when menu items correspond directly to content pages, allowing the menu to reflect your content hierarchy automatically. This keeps navigation logic close to the content it represents.

## Accessing Menus in Templates

After assembly, menus are available through `site.Menus`. The `navigation.MenuEntry` type provides properties like `URL`, `Name`, `Weight`, `Parent`, and `Children`:

```go
{{ $menu := site.Menus.main }}
<ul>
  {{ range $menu.ByWeight }}
    <li class="{{ with .Params.class }}{{ . }}{{ end }}">
      <a href="{{ .URL }}">{{ .Name }}</a>
      {{ if .HasChildren }}
        <ul>
          {{ range .Children }}
            <li><a href="{{ .URL }}">{{ .Name }}</a></li>
          {{ end }}
        </ul>
      {{ end }}
    </li>
  {{ end }}
</ul>

```

The `MenuEntry` struct (defined in [`navigation/menu.go`](https://github.com/gohugoio/hugo/blob/main/navigation/menu.go)) includes `MenuConfig` fields (identifier, name, URL, weight, params) and maintains a slice of child `MenuEntry` pointers for the hierarchical tree.

## Summary

- Hugo's menu system aggregates entries from **site configuration**, **page front matter**, and **automatic section pages** into a unified hierarchical tree during the build process.
- The assembly logic resides in [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go) within `assembleMenus()` (lines 1398–1475), which flattens sources, resolves page references, and builds parent-child relationships.
- Front matter parsing in [`navigation/pagemenus.go`](https://github.com/gohugoio/hugo/blob/main/navigation/pagemenus.go) (lines 44–97) supports string, slice, or structured table formats for defining menu entries per page.
- Site configuration defines static navigation items via the `menus` block, while front matter connects specific content pages to menus using the `menus` key.
- Final menus are exposed to templates as `site.Menus.<name>`, containing `MenuEntry` objects with URL, Name, Weight, Parent, and Children properties.

## Frequently Asked Questions

### What is the difference between defining menus in config versus front matter?

Site configuration ([`config.toml`](https://github.com/gohugoio/hugo/blob/main/config.toml)/`yaml`/`json`) is ideal for static navigation items like "Home" or "About" that may not correspond to specific content files or require custom URLs independent of your content structure. Front matter definitions attach menu entries directly to content pages, making the navigation dynamic and content-driven. According to the Hugo source code in [`navigation/pagemenus.go`](https://github.com/gohugoio/hugo/blob/main/navigation/pagemenus.go), front matter supports three shapes: a single string, a slice of strings, or a structured table with full `MenuConfig` options.

### How do I create nested menu items in Hugo?

Nested menus rely on the `parent` field in your menu entry configuration. First, define a parent item with a unique `identifier` in your site configuration or front matter. Then, set the `parent` field of child entries to match that identifier. During assembly in [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go), Hugo detects these relationships and populates the `Children` slice on each `MenuEntry` struct. In templates, you can check `.HasChildren` and range over `.Children` to render sub-menus.

### Can I add a single page to multiple menus?

Yes. In front matter, use the slice syntax to assign a page to multiple menus simultaneously. For example, `menus: ["main", "footer"]` in YAML or `menus = ["main", "footer"]` in TOML. The parsing logic in [`navigation/pagemenus.go`](https://github.com/gohugoio/hugo/blob/main/navigation/pagemenus.go) detects array values and creates a separate `MenuEntry` for each menu name specified, all pointing to the same page. This allows you to display the same content in different navigation contexts (e.g., main navigation and footer links) without duplication.

### What is sectionPagesMenu and how does it work?

`sectionPagesMenu` is a site configuration option that automatically generates menu entries for your top-level content sections. When you set `sectionPagesMenu = "main"` (or any menu name) in your config, Hugo iterates over all section pages during `assembleMenus()` in [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go) and creates a `MenuEntry` for each section using the section name as the identifier. This provides a quick way to build dynamic navigation for blogs, documentation, or other section-based content structures without manually defining each entry in configuration or front matter.