Wikilinks in Hugo

Internal links, or wikilinks, are links to a page of the site/wiki in the format [[page name]] (note the double square brackets).

From Wikipedia:

To create a so-called internal link to a page on the same wiki (a “wikilink”), use double square brackets wiki markup, [[like this]]. When you preview or save your changes, you will see a link that can be followed to the target page. If the page exists the link is displayed in blue (like the word “create” in the first sentence of this paragraph); if the page does not exist, the link appears red… Note that the colors could be different if the color scheme of the wiki has been changed from the default. Following such a “redlink” to a missing page (whether or not it is actually red) will usually enable the user to create the page.

To markup any arbitrary string of text (not necessarily a page title) as a link, use a “vertical bar” or “pipe” character, like this: [[Help:Categories|category links]] results in the link category links.

  • Wikipedia and MediaWiki
  • Obsidian
  • DokuWiki


Support for Wikilinks will greatly improve interoperability and porting of documents between Obsidian or DokuWiki to Hugo.

A partial workaround and its limitations

Both Obsidian and Hugo support inline links in the format [Wikilinks in Hugo](2022-06-10-markdown-internal-link-wikilinks)

Broken links

Let’s suppose we want to link to this page from the Hugo Technical Details page [file: /content/website-tech/], the paths are:

  • Obsidian path: /content/posts/2022-06-10-markdown-internal-link-wikilinks
  • Hugo path: /posts/2022-06-10-markdown-internal-link-wikilinks

So referencing the page as [Wikilinks in Hugo](/posts/2022-06-10-markdown-internal-link-wikilinks) will result in either Obsidian or Hugo have a broken link.

You could set up an Obsidian vault directly inside your /content folder, but then you cannot have content outside of what is published by Hugo.

A solution is to use relative links [Wikilinks in Hugo](../posts/2022-06-10-markdown-internal-link-wikilinks)

Broken links if “url” is set in page’s Front Matter

The above link will not work in Hugo if a ‘url’ attribute is specified in the Front Matter of the linked page - as it is the case of this page, who has a “url: /2022-06-10-hugo-wikilinks/” attribute.

Hugo requires a shortcode “ref” or “relref” to link to that

Page suggestions and autolinking

One of the nice features of Obsidian is that it suggests pages directly while you type your link. If you type “[[Hugo” it will suggests all the pages relative to Hugo, and provide the correct link to that page. As the vault/site grows bigger, that feature helps a lot.

Obsidian has also a powerful related pages management, showing on a pane both the linking-to and the linked-from pages for easy navigation and suggestions. Also, links around the site/vault are updated when the note’s title is changed.

By using the []() link style, that feature doesn’t work.

GitHub issue

Main Issue request Support wiki-style internal page links #3606

Other material:

Using shortcodes

Shortcodes are not a solution, as the format [[Wiki link]] does not invoke the render-link.html hook:



  • Render hooks are not called with [[]] but only with []() - Useless you can define a custom short code opener
    • hugolib/content_factory.go row 141 does that
    • see hugolib/page__per_output.go:387
      •     if strings.Contains(contentToRender, “{{”) {
      • ….         s := newShortcodeHandler(p.p, p.p.s)

The idea is to insert a if strings.Contains(contentToRender, "[[") in hugolib/page__per_output.go before it is checked for shortcodes (now on row 387) and transform that into a proper link in the format []().

TODO: check if REF and internal link are supposed to behave the same - if yes, then transform it into a REF (RELREF?), otherwise create a shortcode for internal links

Ideas and notes

  • which is executed first?

  • hugo[Git rep]/markup/markup.go

    • GetMarkupConfig()
  • hugo[Git rep]/markup/goldmark/convert.go

  • Markdown Render Hooks

  • Shortcodes

  • Ref e RelRef are in hugolib/page_ref.go

    • func newPageRef(p *pageState) pageRef { return pageRef{p: p} }
    • type pageRef struct { p *pageState }
    • func (p pageRef) Ref(argsm map[string]any) (string, error) { return p.ref(argsm, p.p)
    • func (p pageRef) RelRef(argsm map[string]any) (string, error) { return p.relRef(argsm, p.p) }
    • “”
    • “”
  • See also hugolib/shortcode.go

    • // RelRef is a shortcut to the RelRef method on Page. It passes itself as a context to get better error messages.
    • func (scp *ShortcodeWithPage) RelRef(args map[string]any) (string, error) {    return scp.Page.RelRefFrom(args, scp) }
  • Resources/page/page_nop.go

    • func (p *nopPage) RelRefFrom(argsm map[string]any, source any) (string, error) {    return “”, nil }
  • resouces/page/page.go

    • // RefProvider provides the methods needed to create reflinks to pages.
    • type RefProvider interface { RelRef(argsm map[string]any) (string, error)
  • ==tpl/tplimpl/embedded/emplates/shortcodes/relref.html==

    • {{ relref . .Params }}
  • tpl/urls/init.go

    • ns.AddMethodMapping(ctx.RelRef,[]string{“relref”},[][2]string{},)
  • ==tpl/urls/urls.go==

    • // RelRef returns the relative URL path to a given content item from Page p.
    • func (ns *Namespace) RelRef(p any, args any) (template.HTML, error) {… LOTS OF THINGS … s, err := pp.RelRef(argsm) return template.HTML(s), err }

Render hooks are called based on the output of the Goldmark renderer - the renderer must be called first ( hugolib/page__per_output.go row 512)