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.Pageitself 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), plusfmt.Stringer. - Purpose: The entire template-facing API for a content page. Everything a Go template can ask of a page is defined here.
pageStateinhugolibis the sole concrete implementation. - Implementations:
hugolib.pageState(unexported). Accessed by templates always through thepage.Pageinterface. - Design quality: Intentionally broad — this is a deliberate facade. The composable sub-interfaces (
ContentProvider,PageMetaProvider,ChildCareProvider, etc.) follow ISP; the aggregatePageinterface 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.Pageis a subset used in menus). The trade-off is thatPagehas ~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.
ProviderProvideris a compile-time registered factory (goldmark, asciidocext, pandoc, org-mode, rst).Provideris a per-document-context factory.Converterdoes the actual conversion (raw bytes → HTML). - Implementations:
ProviderProvider:goldmark.Provider,asciidocext.Provider,pandoc.Provider,rst.Provider,org.ProviderProvider:newConverter(generic adapter), per-markup concrete providersConverter: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
ParseRendererextension interface (goldmark only) adds parse/render separation without polluting the baseConverterinterface.
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,Managerrecords which identities were accessed (forming a dependency graph). On file change,SignalRebuilderpropagates 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),NopManagerSignalRebuilder:deps.Deps(the root rebuild trigger)
- Design quality: Very well-segregated.
Identityis a minimal 1-method interface (comparable/hashable via its use as a map key).Managerdeliberately keepsforEeachIdentityunexported to prevent external implementations from bypassing the internal traversal protocol. TheDependencyManagerProvider,DependencyManagerScopedProvider, andForEeachIdentityProviderhelper interfaces follow ISP perfectly. TheIsProbablyDependentProvider/IsProbablyDependencyProvideroptional 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 throughoutdeps,markup/converter,resources, andhugofs. - Implementations:
allconfig.ConfigProvider(wrapsallconfig.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 companionconfig.Providerinterface (11 methods, rawGet/Setmap-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/templateandhtml/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 forkedtext/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 letstplimplcontrol the clone lifecycle without the caller needing to know.
hugofs.FileMetaInfo#
- Package:
github.com/gohugoio/hugo/hugofs - File:
hugofs/fileinfo.go:159 - Methods:
— whereFileMetaInfo (embeds fs.FileInfo): Meta() *FileMetaFileMetacarriesPathInfo,Module,Component,Weight,SourceRoot,SitesMatrix,OpenFunc,InclusionFilter, etc. - Purpose: Extends the stdlib
fs.FileInfowith Hugo-specific metadata needed to route files through the overlay filesystem. Every file traversed throughhugofscarries its module origin, mount component (content/layout/static/…), and site matrix membership in theFileMetapayload. - Implementations:
fileInfoMeta(unexported struct inhugofs); virtually allafero-level file info objects in Hugo are wrapped to carryFileMeta. - 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 toFileMetaInfo. TheMetaProvider(1-method helper interface:Meta() *FileMeta) is used whereFileMetaInfowould be too broad. The mutableFileMetastruct (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 arepage.Page(~50 effective via embedding) andconfig.AllProvider(~45 methods), both of which are intentionally large aggregation facades at system boundaries.Embedding: Pervasive.
page.Pageis almost entirely composed of embedded sub-interfaces.identity.ManagerembedsIdentity.hugofs.FileMetaInfoembedsfs.FileInfo. Theresource.Resourceinterface (used by both pages and non-page assets) embedsResourceLinksProvider,ResourceNameTitleProvider,ResourceMetaProvider,MediaTypeProvider, etc. Interface embedding is Hugo’s primary composition mechanism.Implicit satisfaction: Mostly consumer-defined. Key interfaces like
Converter,Provider,Identity,SignalRebuilderare defined in the package that uses them, and concrete implementations in separate packages satisfy them implicitly.page.Pageis an exception — it is defined inresources/page(the domain package) and implemented inhugolib(the orchestration package), creating a deliberate inversion sohugolibdepends on the domain, not vice versa.stdlib interfaces used:
fs.FileInfoembedded byhugofs.FileMetaInfoio.Reader,io.Writer,io.Closerused throughouthugofsandtransformfmt.Stringerembedded bypage.Pagesort.Interfaceused innavigationfor menu sortinghttp.Handlerinlivereload
Key abstractions#
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.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 optionalParseRendererextension interface adds goldmark-specific parse/render separation without breaking other converters.identity.Identity+identity.Manager— The most architecturally sophisticated set of interfaces. They underpin the incremental rebuild system.Identitybeing a single-method hashable interface allows it to serve as a map key, enabling O(1) lookup in the dependency graph.Manageris the dependency accumulator that makes fine-grained invalidation possible.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.hugofs.FileMetaInfo— The virtual filesystem contract. By extendingfs.FileInfowithMeta() *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
ProviderProviderinmarkup/markup.goadds 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.Pluginor 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
extendedtag adds CSS/JS processing;withdeployadds cloud deployment. Each edition registers additionalProviderProviderimplementations and resource transformers viainit()functions gated by build tags.