Skip to content

Hugo Modules & Theme Development — Complete Guide

DodaTech Updated 2026-06-22 7 min read

In this tutorial, you'll learn about Hugo Modules & Theme Development. We cover key concepts, practical examples, and best practices.

Hugo modules provide a built-in dependency management system for themes, components, and shared assets — replacing Git submodules with Go-based module resolution for cleaner, more maintainable static site projects.

What You'll Learn

Why It Matters

Managing theme dependencies with Git submodules creates bloated repositories, merge conflicts, and versioning headaches. Hugo Modules solve these problems with semantic versioning, proxy caching, and automatic dependency resolution — all built into Hugo's Go-based architecture. Understanding modules lets you compose sites from reusable components, share themes across projects, and keep your content separate from presentation logic.

Real-World Use

A documentation team maintains a shared theme across five product documentation sites using Hugo Modules, pushing updates to a single theme repository. An agency builds a component library of reusable shortcodes and partials distributed as modules. A developer vendors third-party themes with exact version pinning for reproducible builds.

Module Architecture

flowchart TD
  A[Site Repository] --> B[Theme Module]
  A --> C[Component Module]
  A --> D[Asset Module]
  B --> E[GitHub Theme Repo]
  C --> F[Shared Shortcodes]
  D --> G[SCSS/JS Library]
  E --> H[Go Module Proxy]
  F --> H
  G --> H
  H --> I[Build Cache]
  I --> J[Hugo Build]
  style A fill:#f90,color:#fff
  style H fill:#09f,color:#fff
â„šī¸ Info

Prerequisites: Familiarity with Hugo templates, YAML configuration, and basic Git operations. Hugo Modules require Hugo 0.56+ (0.128+ recommended).

Understanding Hugo Modules

What Is a Module?

A Hugo module is any directory containing a go.mod file and Hugo-compatible content, layouts, assets, or i18n files. Modules can provide:

Module Type What It Contains Example
Theme Layouts, templates, styles hextra, docsy
Component Shortcodes, partials, functions social-share, toc-generator
Asset SCSS, JS, images bootstrap-scss, tailwind-components
Content Default pages, archetypes blog-starter, landing-page

How Modules Differ from Submodules

Feature Git Submodules Hugo Modules
Versioning Git refs only Semantic versioning (v1.2.3)
Dependency resolution Manual Automatic, transitive
Proxy caching No Go module proxy
Vendoring Manual git clone hugo mod vendor
Overrides Difficult replace directives
Updates Manual git commands hugo mod get -u

Setting Up Hugo Modules

1. Initialize a Module

# Initialize the module system in your site
hugo mod init github.com/yourusername/your-site

Expected behavior: Hugo creates a go.mod file in the site root. This file tracks all module dependencies and their versions.

2. Add a Theme Module

# hugo.toml — Using a theme as a module
baseURL = "https://example.com"
theme = "hextra"

[module]
  [[module.imports]]
    path = "github.com/imfing/hextra"

Or in the newer Hugo config format:

# hugo.yaml
baseURL: "https://example.com"
theme: hextra
module:
  imports:
    - path: github.com/imfing/hextra

Expected behavior: Hugo downloads the hextra module and all its dependencies. The theme is available immediately without manual cloning. Running hugo mod tidy cleans up unused dependencies.

3. Creating a Custom Module

Create a directory with the module structure:

my-module/
├── go.mod          # module declaration
├── layouts/        # Go templates
│   ├── _default/
│   └── shortcodes/
├── assets/         # SCSS, JS
├── i18n/           # Translations
└── data/           # Data files

Initialize it:

cd my-module
hugo mod init github.com/yourusername/my-module

4. Using Local Modules for Development

# hugo.toml — Local development overrides
[module]
  [[module.imports]]
    path = "github.com/yourusername/my-module"

  [[module.mounts]]
    source = "my-module"
    target = "github.com/yourusername/my-module"

Expected behavior: During development, Hugo uses the local filesystem version of my-module. For production, the mount is removed and Hugo fetches the published version from the module proxy.

Theme Development with Modules

Component-Based Architecture

Organize your theme as independent components:

my-theme/
├── go.mod
├── layouts/
│   ├── _default/
│   │   ├── baseof.html
│   │   ├── single.html
│   │   └── list.html
│   ├── partials/
│   │   ├── head.html
│   │   ├── header.html
│   │   ├── footer.html
│   │   └── sidebar.html
│   └── shortcodes/
│       ├── card.html
│       ├── alert.html
│       └── figure.html
├── assets/
│   ├── scss/
│   │   ├── main.scss
│   │   └── variables.scss
│   └── js/
│       └── main.js
└── theme.toml

Overriding Module Templates

Hugo's lookup order prioritizes site-level templates over module templates. To override a partial from a module:

{{/* layouts/partials/header.html — Site-level override */}}
{{/* Hugo looks here first, then in module directories */}}
<header class="site-header">
  <nav>{{ partial "nav.html" . }}</nav>
</header>

Composing Multiple Modules

# hugo.toml — Multiple module imports
[module]
  [[module.imports]]
    path = "github.com/yourusername/theme-core"
  [[module.imports]]
    path = "github.com/yourusername/social-share"
  [[module.imports]]
    path = "github.com/yourusername/analytics-component"

  [[module.imports]]
    path = "github.com/twbs/bootstrap"
    disable = false

Expected behavior: Hugo merges all imported modules following the lookup order. The theme-core provides base templates, social-share adds share buttons, and analytics-component inserts tracking snippets.

Version Management

Pinning Versions

# Pin to a specific version
hugo mod get github.com/imfing/hextra@v0.12.3

# Update to latest patch
hugo mod get -u github.com/imfing/hextra

# Update all modules
hugo mod get -u ./...

Vendoring Dependencies

# Copy all module dependencies into your repository
hugo mod vendor

Expected behavior: hugo mod vendor creates a _vendor/ directory containing all module source files. This ensures reproducible builds even if the upstream module becomes unavailable. Run hugo --ignoreVendor to temporarily ignore vendored modules.

Common Errors

1. Module Not Found

If Hugo cannot resolve a module path, ensure the go.mod file at the module root exists and the repository URL is correct. Run hugo mod tidy to update the go.sum file and clean up unused entries.

2. Template Lookup Order Confusion

When multiple modules provide the same template, Hugo uses a strict lookup order: site layouts first, then module imports in declaration order. If the wrong template is rendering, check the import order in your hugo.toml.

3. Missing or Incompatible Module Version

A module update may introduce breaking changes. Pin to a known working version with hugo mod get modulename@v1.2.3. Use hugo mod graph to visualize all dependency relationships.

4. Asset Path Conflicts

Two modules providing the same asset path (e.g., assets/scss/main.scss) cause unpredictable results. Namespace your assets with a prefix: assets/scss/modulename/main.scss and import them explicitly.

5. Module Proxy Connection Issues

Corporate or restricted networks may block the Go module proxy. Configure a custom proxy or disable it:

# Use direct connections instead of proxy
export GOPROXY=direct
# Or use a corporate proxy
export GOPROXY=https://corporate-proxy.example.com,direct

Practice Questions

1. What command initializes Hugo Modules in a project?

hugo mod init <module-path> creates the go.mod file and enables module-based dependency management.

2. How do you override a template from an imported module?

Place a template with the same path in your site's layouts/ directory. Hugo's lookup order checks site-level templates before module templates.

3. What is the purpose of vendoring modules?

Vendoring copies all module source files into the _vendor/ directory, ensuring reproducible builds when the module proxy or source repositories are unavailable.

4. How do you update a single module to its latest version?

Run hugo mod get -u github.com/username/modulename to update a specific module while leaving others unchanged.

5. Challenge: Create a Hugo module that provides a custom figure shortcode with lazy loading, WebP sources, and a caption parameter. Publish it to GitHub and import it into a test site. Verify the shortcode works by rendering an image with both standard and WebP fallback.

Mini Project: Build a Component Library as a Hugo Module

Create a reusable component library distributed as a Hugo module:

  1. Initialize a new Hugo module with hugo mod init github.com/yourusername/ui-components
  2. Create the following components:
    • A notice shortcode with type (info, warning, error) and title parameters
    • A breadcrumb partial that generates breadcrumb navigation using .Ancestors
    • A pagination partial with numbered page links
    • A toc partial that renders heading-based table of contents
  3. Include SCSS for each component in assets/scss/ui-components/
  4. Import the module in a test site and use each component
  5. Publish the module to GitHub and document the import instructions

Test the module:

hugo mod get github.com/yourusername/ui-components@latest
hugo server -D --disableFastRender

Verify each component renders correctly and styles are applied. Use hugo mod graph to confirm no circular dependencies exist.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro