Hugo — Interfaces#

Interface catalog#

page.Page#

  • Package: github.com/gohugoio/hugo/resources/page
  • File: resources/page/page.go:172
  • Methods: Defined entirely via embedding — page.Page itself has zero explicit methods, composing ~20 smaller provider interfaces: MarkupProvider, ContentProvider, TableOfContentsProvider, PageWithoutContent (which in turn embeds: RawContentProvider, RenderShortcodesProvider, resource.Resource, PageMetaProvider, FileProvider, GitInfoProvider, OutputFormatsProvider, AlternativeOutputFormatsProvider, ChildCareProvider, TreeProvider, InSectionPositioner, PageRenderProvider, PaginatorProvider, Positioner, navigation.PageMenusProvider, GetPageProvider, RefProvider, TranslationsProvider, SiteProvider, SitesProvider, ShortcodeInfoProvider, compare.Eqer, hstore.StoreProvider, RelatedKeywordsProvider), plus fmt.Stringer.
  • Purpose: The entire template-facing API for a content page. Everything a Go template can ask of a page is defined here. pageState in hugolib is the sole concrete implementation.
  • Implementations: hugolib.pageState (unexported). Accessed by templates always through the page.Page interface.
  • Design quality: Intentionally broad — this is a deliberate facade. The composable sub-interfaces (ContentProvider, PageMetaProvider, ChildCareProvider, etc.) follow ISP; the aggregate Page interface does not. This is a pragmatic choice: templates receive one uniform value rather than a bag of smaller types. The sub-interfaces are reusable (e.g., navigation.Page is a subset used in menus). The trade-off is that Page has ~50 effective methods, making it hard to mock and impossible to satisfy with a hand-written stub.

converter.Converter / Provider / ProviderProvider#

  • Package: github.com/gohugoio/hugo/markup/converter
  • File: markup/converter/converter.go:45,50,90
  • Methods:
    ProviderProvider:
        New(cfg ProviderConfig) (Provider, error)
    
    Provider:
        New(ctx DocumentContext) (Converter, error)
        Name() string
    
    Converter:
        Convert(ctx RenderContext) (ResultRender, error)
  • Purpose: Three-level factory chain for pluggable markup converters. ProviderProvider is a compile-time registered factory (goldmark, asciidocext, pandoc, org-mode, rst). Provider is a per-document-context factory. Converter does the actual conversion (raw bytes → HTML).
  • Implementations:
    • ProviderProvider: goldmark.Provider, asciidocext.Provider, pandoc.Provider, rst.Provider, org.Provider
    • Provider: newConverter (generic adapter), per-markup concrete providers
    • Converter: goldmarkConverter, nopConverter (sentinel)
  • Design quality: Excellent ISP. Each interface has 1–2 methods. The three-level abstraction (factory-of-factories) is justified because the markup config (goldmark extensions, Chroma highlight settings) is resolved at startup, while the per-document context (page path, document lookup function) is resolved at render time. The optional ParseRenderer extension interface (goldmark only) adds parse/render separation without polluting the base Converter interface.

identity.Identity / Manager / SignalRebuilder#

  • Package: github.com/gohugoio/hugo/identity
  • File: identity/identity.go:227,279,244
  • Methods:
    Identity:
        IdentifierBase() string
    
    Manager (embeds Identity):
        AddIdentity(ids ...Identity)
        AddIdentityForEach(ids ...ForEeachIdentityProvider)
        GetIdentity() Identity
        Reset()
        forEeachIdentity(func(id Identity) bool) bool   // unexported, package-internal
    
    SignalRebuilder:
        SignalRebuild(ids ...Identity)
  • Purpose: Dependency tracking for Hugo’s incremental rebuild system. Every page, template, and resource carries an Identity. During render, Manager records which identities were accessed (forming a dependency graph). On file change, SignalRebuilder propagates changed identities, and only affected pages are re-rendered.
  • Implementations:
    • Identity: StringIdentity (string wrapper), orIdentity (union), AnonymousIdentity (sentinel)
    • Manager: identityManager (concrete), nopManager (no-op sentinel), NopManager
    • SignalRebuilder: deps.Deps (the root rebuild trigger)
  • Design quality: Very well-segregated. Identity is a minimal 1-method interface (comparable/hashable via its use as a map key). Manager deliberately keeps forEeachIdentity unexported to prevent external implementations from bypassing the internal traversal protocol. The DependencyManagerProvider, DependencyManagerScopedProvider, and ForEeachIdentityProvider helper interfaces follow ISP perfectly. The IsProbablyDependentProvider / IsProbablyDependencyProvider optional interfaces add approximate matching without changing core contracts.

config.AllProvider#

  • Package: github.com/gohugoio/hugo/config
  • File: config/configProvider.go:28
  • Methods: ~45 typed accessor methods, including: Language() any, BaseURL() urls.BaseURL, Environment() string, Dirs() CommonDirs, GetConfigSection(string) any, GetConfig() any, IsKindEnabled(string) bool, Timeout() time.Duration, WorkingDir() string, NewIdentityManager(...) identity.Manager, etc.
  • Purpose: The typed, read-only view of Hugo’s merged configuration given to all subsystems. Implemented by allconfig.ConfigProvider (the real implementation) and used as the parameter type throughout deps, markup/converter, resources, and hugofs.
  • Implementations: allconfig.ConfigProvider (wraps allconfig.Configs). Tests use hand-wired minimal structs.
  • Design quality: Somewhat broad (45 methods), but each method is a named config concept rather than a raw key lookup. This avoids stringly-typed GetString("baseURL") calls throughout the codebase, trading interface breadth for compile-time type safety. The companion config.Provider interface (11 methods, raw Get/Set map-like API) is used internally for config merging before the typed projection is built. The split is deliberate and clean.

tpl.Template#

  • Package: github.com/gohugoio/hugo/tpl
  • File: tpl/template.go:36
  • Methods:
    Template:
        Name() string
        Prepare() (*texttemplate.Template, error)
  • Purpose: Common interface bridging Go’s text/template and html/template (Hugo maintains a fork of both). Prepare() triggers lazy compilation — templates are parsed eagerly but their execution trees are cloned lazily for concurrent execution.
  • Implementations: tplimpl.templateState (wraps the forked text/template.Template), tplimpl.shortcodeTemplate
  • Design quality: Minimal by design (2 methods). The underlying template machinery is intentionally hidden behind this interface. The lazy Prepare() pattern (clone-on-use) is what makes concurrent page rendering safe; exposing it as an interface method lets tplimpl control the clone lifecycle without the caller needing to know.

hugofs.FileMetaInfo#

  • Package: github.com/gohugoio/hugo/hugofs
  • File: hugofs/fileinfo.go:159
  • Methods:
    FileMetaInfo (embeds fs.FileInfo):
        Meta() *FileMeta
    — where FileMeta carries PathInfo, Module, Component, Weight, SourceRoot, SitesMatrix, OpenFunc, InclusionFilter, etc.
  • Purpose: Extends the stdlib fs.FileInfo with Hugo-specific metadata needed to route files through the overlay filesystem. Every file traversed through hugofs carries its module origin, mount component (content/layout/static/…), and site matrix membership in the FileMeta payload.
  • Implementations: fileInfoMeta (unexported struct in hugofs); virtually all afero-level file info objects in Hugo are wrapped to carry FileMeta.
  • Design quality: The embedding of fs.FileInfo (stdlib) is idiomatic Go extension — callers that only need standard file info use it directly; callers that need Hugo metadata type-assert to FileMetaInfo. The MetaProvider (1-method helper interface: Meta() *FileMeta) is used where FileMetaInfo would be too broad. The mutable FileMeta struct (rather than interface methods) trades some encapsulation for merge-ability (FileMeta.Merge()), which is needed when overlaying module mounts.

Interface patterns#

  • Size distribution: Hugo follows ISP closely in most packages. The majority of interfaces have 1–4 methods (Identity, Converter, Template, SignalRebuilder, MetaProvider, ResourceGetter, Source, etc.). Exceptions are page.Page (~50 effective via embedding) and config.AllProvider (~45 methods), both of which are intentionally large aggregation facades at system boundaries.

  • Embedding: Pervasive. page.Page is almost entirely composed of embedded sub-interfaces. identity.Manager embeds Identity. hugofs.FileMetaInfo embeds fs.FileInfo. The resource.Resource interface (used by both pages and non-page assets) embeds ResourceLinksProvider, ResourceNameTitleProvider, ResourceMetaProvider, MediaTypeProvider, etc. Interface embedding is Hugo’s primary composition mechanism.

  • Implicit satisfaction: Mostly consumer-defined. Key interfaces like Converter, Provider, Identity, SignalRebuilder are defined in the package that uses them, and concrete implementations in separate packages satisfy them implicitly. page.Page is an exception — it is defined in resources/page (the domain package) and implemented in hugolib (the orchestration package), creating a deliberate inversion so hugolib depends on the domain, not vice versa.

  • stdlib interfaces used:

    • fs.FileInfo embedded by hugofs.FileMetaInfo
    • io.Reader, io.Writer, io.Closer used throughout hugofs and transform
    • fmt.Stringer embedded by page.Page
    • sort.Interface used in navigation for menu sorting
    • http.Handler in livereload

Key abstractions#

  1. page.Page — The most important interface in Hugo. Everything templates touch is mediated through this interface. Its design as a composition of small provider interfaces means individual capabilities can be tested in isolation, even if the aggregate is untestable.

  2. converter.Converter / Provider / ProviderProvider — The primary extension point for Hugo. These three interfaces form a clean three-level factory chain that allows new markup languages to be plugged in at compile time. The optional ParseRenderer extension interface adds goldmark-specific parse/render separation without breaking other converters.

  3. identity.Identity + identity.Manager — The most architecturally sophisticated set of interfaces. They underpin the incremental rebuild system. Identity being a single-method hashable interface allows it to serve as a map key, enabling O(1) lookup in the dependency graph. Manager is the dependency accumulator that makes fine-grained invalidation possible.

  4. config.AllProvider — The typed configuration facade. By defining all config access as interface methods rather than raw string keys, Hugo prevents typos and enables compile-time verification of config consumption across all subsystems.

  5. hugofs.FileMetaInfo — The virtual filesystem contract. By extending fs.FileInfo with Meta() *FileMeta, Hugo attaches routing metadata (module origin, component type, site matrix) to every file at the filesystem layer, keeping upper layers (content processing, template lookup) free of filesystem-resolution logic.


Interface-driven extensibility#

Hugo’s extensibility is narrow and intentional:

  • Markup converters are the primary runtime extension point. Registering a new ProviderProvider in markup/markup.go adds a new markup language. All five built-in converters (goldmark, asciidocext, pandoc, rst, org) satisfy the same three-level factory chain.

  • Template hooks (markup/converter/hooks/hooks.go) provide a second layer of extensibility. Users can override rendering of links (LinkRenderer), code blocks (CodeBlockRenderer), headings (HeadingRenderer), blockquotes (BlockquoteRenderer), and tables (TableRenderer) by providing Hugo templates that satisfy these interfaces. This is template-side extension, not Go-code extension.

  • Resource transforms (Hugo Pipes) expose a transform chain via resource.ResourceTransformations, but the transform implementations are all built-in.

  • No runtime Go plugins. Hugo explicitly does not support plugin.Plugin or hashicorp/go-plugin. Extensibility for end users is through themes, Hugo Modules, and template hooks — not through loading external Go code at runtime.

  • Build-tag editions provide compile-time extensibility: the extended tag adds CSS/JS processing; withdeploy adds cloud deployment. Each edition registers additional ProviderProvider implementations and resource transformers via init() functions gated by build tags.