Hugo makes it easy to link documents together with the ref and relref shortcodes. These shortcodes are also used to safely provide links to headings inside of your content, whether across documents or within a document. The only difference between ref and relref is whether the resulting URL is absolute ( or relative (/about/).

Using ref and relref

{{< ref "document" >}}
{{< ref "#anchor" >}}
{{< ref "document#anchor" >}}
{{< relref "document" >}}
{{< relref "#anchor" >}}
{{< relref "document#anchor" >}}

The single parameter to ref is a string with a content document name (, an in-document anchor (#who), or both (

Document Names

The document name is the name of a document including the format extension; this may be just the filename, or the relative path from the content/ directory. With a document content/blog/, either format will produce the same result.

{{< relref "blog/" >}} ⇒ `/blog/post/`
{{< relref "" >}} ⇒ `/blog/post/`

If you have multiple sections with the same filename, you should only use the relative path format, because the behaviour is undefined. So, if I also have a document link/, the output of ref is unknown for

{{< relref "blog/" >}} ⇒ `/blog/post/`
{{< relref "" >}} ⇒ `/blog/post/` (maybe)
{{< relref "" >}} ⇒ `/link/post/` (maybe)
{{< relref "link/" >}} ⇒ `/link/post/`

A relative document name must not begin with a slash (/).

{{< relref "/blog/" >}} ⇒ `""`


When an anchor is provided by itself, the current page’s unique identifier will be appended; when an anchor is provided with a document name, the found page’s unique identifier will be appended.

{{< relref "#who" >}} ⇒ `#who:9decaf7`
{{< relref "blog/" >}} ⇒ `/blog/post/#who:badcafe`

More information about document unique identifiers and headings can be found below.


  • {{< ref "blog/" >}}
  • {{< ref "" >}}
  • {{< relref "" >}}/blog/post/
  • {{< relref "blog/" >}}/blog/post/#tldr:caffebad
  • {{< ref "#tldr" >}}#tldr:badcaffe
  • {{< relref "#tldr" >}}#tldr:badcaffe

Hugo Heading Anchors

When using Markdown document types, Hugo generates heading anchors automatically. The generated anchor for this section is hugo-heading-anchors. Because the heading anchors are generated automatically, Hugo takes some effort to ensure that heading anchors are unique both inside a document and across the entire site.

Ensuring heading uniqueness across the site is accomplished with a unique identifier for each document based on its path. Unless a document is renamed or moved between sections in the filesystem, the unique identifier for the document will not change: blog/ will always have a unique identifier of 81df004c333b392d34a49fd3a91ba720.

ref and relref were added so you can make these reference links without having to know the document’s unique identifier. (The links in document tables of contents are automatically up-to-date with this value.)

{{< relref "doc/extras/" >}}

What follows is a deeper discussion of why and how Hugo generates heading anchors. It is not necessary to know this to use ref and relref, but it may be useful in understanding how some anchors may not match your expectations.

How to Generate a Heading Anchor

Convert the text of the heading to lowercase.

Hugo: A Fast & Modern Static Web Engine
hugo: a fast & modern static web engine

Replace anything that isn’t an ASCII letter (a-z) or number (0-9) with a dash (-).

hugo: a fast & modern static web engine

Get rid of extra dashes.


You have just converting the text of a heading to a suitable anchor. If your document has unique heading text, all of the anchors will be unique, too.

Specifying Heading Anchors

You can also tell Hugo to use a particular heading anchor.

# Hugo: A Fast & Modern Static Web Engine {#hugo-main}

Hugo will use hugo-main as the heading anchor.

What About Duplicate Heading Anchors?

The technique outlined above works well enough, but some documents have headings with identical text, like the shortcodes page—there are three headings with the text “Example”. You can specify heading anchors manually:

### Example {#example-1}
### Example {#example-2}
### Example {#example-3}

It’s easy to forget to do that all the time, and Hugo is smart enough to do it for you. It just adds -x to the end of each heading it has already seen.

  • ### Exampleexample
  • ### Exampleexample-1
  • ### Exampleexample-2

Sometimes it’s a little harder, but Hugo can recover from those, too, by adding more suffixes:

  • # Headingheading
  • # Heading 1heading-1
  • # Headingheading-1-1
  • # Headingheading-1-2
  • # Heading 1heading-2

This can even affect specified heading anchors that come after a generated heading anchor.

  • # My Headingmy-heading
  • # My Heading {#my-heading}my-heading-1

This particular collision and override is unfortunate, but unavoidable because Hugo processes each heading for collision detection as it sees it during conversion.

This technique works well for documents rendered on individual pages, like blog posts. What about on Hugo list pages?

Unique Heading Anchors in Lists

Hugo converts each document from Markdown independently. it doesn’t know that blog/ has an “Example” heading that will collide with the “Example” heading in blog/ Even if it did know this, the addition of blog/ should not cause the anchors for the headings in the other blog posts to change.

Enter the document’s unique identifier. To prevent this sort of collision on list pages, Hugo always appends the document’s to a generated heading anchor. So, the “Example” heading in blog/ actually turns into #example:81df004…, and the “Example” heading in blog/ actually turns into #example:8cf1599…. All you have to know is the heading anchor that was generated, not the document identifier; ref and relref take care of the rest for you.

<a href='{{< relref "blog/" >}}'>Post Example</a>
<a href='…'>Post Example</a>

[Post Two Example]({{< relref "blog/" >}})
<a href='…'>Post Two Example</a>

Now you know.