Content adapters

A content adapter is a Go template named _content.gotmpl that lives inside the content/ tree. Hugo runs it while building the site. The template can call AddPage to create pages dynamically.

Use an adapter when data records need to become real Hugo pages. Keep simpler reference tables on Data-driven pages.

Basic shape#

{{ $content := dict
  "mediaType" "text/markdown"
  "value" "A short generated page body."
}}

{{ $page := dict
  "content" $content
  "kind" "page"
  "path" "generated-page"
  "title" "Generated Page"
}}

{{ .AddPage $page }}

That creates a page at a path relative to the adapter. If the adapter lives at content/resources/books/_content.gotmpl, the generated page becomes /resources/books/generated-page/.

Generated pages are still Hugo pages. They can use section layouts, taxonomies, RSS feeds, language alternates, search indexes, and the same partials as hand-written content.

The Books adapter reads hugo.Data.books.books, loops through each book, and creates one page per item. The page body comes from the book description. The page parameters carry everything the layout needs: authors, cover, status, publication details, source links, subjects, and provenance.

The simplified shape looks like this:

{{ .EnableAllLanguages }}
{{ $adapter := . }}

{{ range $book := hugo.Data.books.books }}
  {{ $content := dict
    "mediaType" "text/markdown"
    "value" ($book.description | default "")
  }}

  {{ $params := dict
    "authors" $book.authors
    "cover" $book.cover
    "status" $book.status
  }}

  {{ $page := dict
    "content" $content
    "kind" "page"
    "params" $params
    "path" $book.id
    "title" $book.title
  }}

  {{ $adapter.AddPage $page }}
{{ end }}

Keep $adapter := . near the top because . changes inside range and with. Holding the adapter object in a variable makes the call to AddPage explicit and avoids surprising scope issues.

The Links section uses the same pattern: read the YAML data, build a page map, and call AddPage. The difference is the layout. A link page cares about the outbound URL, short URLs, source, and classification. A book page cares about cover, authors, status, and bibliographic details.

Page data#

The page map passed to AddPage is the bridge between data and Hugo.

FieldPurpose
pathThe generated page path, relative to the adapter
titleThe page title
content.mediaTypeThe format of the generated body
content.valueThe generated body content
paramsCustom values available to layouts as .Params
datesdate, lastmod, and related page dates
kindUsually page for generated single pages

The data file can stay data-shaped, while the adapter translates it into Hugo-shaped pages.

When to use one#

Reach for a content adapter when most of these are true:

  • the source of truth is structured data
  • one data item should become one Hugo page
  • the pages share a stable shape
  • the item count is large enough that hand-written files become noise
  • generated pages need to behave like normal pages for search, feeds, language alternates, or taxonomies

That is the adapter’s sweet spot: translating data-shaped records into Hugo-shaped pages at build time.

When not to use one#

Most data-driven reference pages use the simpler pattern: one explicit Markdown file plus one custom layout or shortcode that reads from data/.

That pattern is better when the page itself is the artifact. The recommendations page, glossary page, North American Numbering Plan page, and SSID list pages are all examples: data-driven, but not trying to create hundreds of independent child pages.

A small split into two or three durable pages does not justify an adapter either. Those pages benefit from explicit Markdown files for front matter, aliases, search, and collection behaviour; an adapter would add more machinery than value.

Why not generate Markdown files#

A script could create one Markdown file per book or link. That would work, but it would add a lot of generated files to the repository. A content adapter keeps the boundary cleaner:

  • raw imports live in csv/
  • canonical data lives in data/
  • generated pages exist only at build time
  • layouts stay responsible for presentation

The repository contains the source and the rules, not a pile of disposable output.

Trade-offs#

Content adapters move some page creation logic into Go templates, which is not always the most comfortable programming environment. A few rules help:

  • keep heavy cleanup and API fetching in scripts
  • keep _content.gotmpl focused on mapping data to pages
  • make paths stable before publishing
  • store provenance in the data file
  • use a normal layout for the actual HTML

Let scripts prepare the data, let the content adapter create the pages, and let layouts make them readable.