Add Attributes to Markdown Links With Hugo Render Hooks

Markdown gives a link a destination, some text, and optionally a title. That is usually enough for an article, but a site’s HTML may need to distinguish external links or add a consistent class.

I would not put custom HTML into every post for that. A Hugo link render hook keeps the content portable and puts site-wide link behaviour in one template.

Create this file:

layouts/
└── _default/
    └── _markup/
        └── render-link.html

Start with the HTML Hugo would normally produce:

<a href="{{ .Destination | safeURL }}">
  {{ .Text | safeHTML }}
</a>

Now ordinary Markdown remains ordinary:

[MDN](https://developer.mozilla.org/)

Without the hook, that becomes ordinary HTML:

<a href="https://developer.mozilla.org/">
  MDN
</a>

The hook decides how that link is rendered everywhere.

A small test can identify an absolute HTTP destination:

{{ $external := strings.HasPrefix .Destination "http" }}

Use it when rendering the attributes:

<a
  href="{{ .Destination | safeURL }}"
  {{ if $external }}
    class="external-link"
    rel="noopener noreferrer"
  {{ end }}
>
  {{ .Text | safeHTML }}
</a>

That condition is intentionally simple. A real site may also need distinct handling for fragment links, mailto:, and tel: destinations:

{{ $isHTTP := strings.HasPrefix .Destination "http" }}
{{ $isMail := strings.HasPrefix .Destination "mailto:" }}
{{ $isTel := strings.HasPrefix .Destination "tel:" }}

The right conditions depend on what the template actually changes. Do not classify five kinds of link if they all produce the same HTML.

Preserve optional Markdown titles

Markdown supports a title as well as visible text:

[Example](https://example.com "Example website")

The hook needs to output it explicitly:

<a
  href="{{ .Destination | safeURL }}"
  {{ with .Title }}
    title="{{ . }}"
  {{ end }}
>
  {{ .Text | safeHTML }}
</a>

Authors can keep using a normal link elsewhere:

[Example](https://example.com/)

This is an easy detail to lose when replacing Hugo’s default rendering.

An external marker belongs in the presentation

If external links need a subtle marker, the class gives CSS somewhere to attach it:

.external-link::after {
  content: " ↗";
  font-size: 0.8em;
}

The arrow is decorative, so generated content avoids adding meaningless icon markup to every link.

I generally would not force all external links into new tabs. People can choose a new tab themselves, while an unexpected one changes normal browser behaviour. If a particular link genuinely needs target="_blank", include rel="noopener noreferrer" too.

A render hook affects every Markdown link, including the ones that are easy to forget. Before using it across the site, check internal paths, absolute URLs, fragments, email and telephone links, titles, and escaped text.

That wide reach is both the benefit and the risk. I like render hooks because authors keep writing plain Markdown while the site owns its HTML rules. It works well as long as the hook preserves the simple cases instead of only perfecting the external one.