Skip to main content

Hugo Modules

View SourceEdit this page (external link)

This page covers Hugo Modules and the corresponding hugo mod command in detail.

What Is a Module Link to this heading

A module is the basic unit Hugo uses to organize content. A module can be a complete Hugo project, a theme you've installed, for example, or it can be a small, reusable package that provides just one type of component: content, layouts, assets, data, i18n, static, or archetypes.

Modules can be combined and nested arbitrarily, and can also mount external directories, even directories from non-Hugo projects. Everything ultimately merges into the same UFS.

Initialization and Imports Link to this heading

Your project must become a module itself before it can import other modules:

hugo mod init github.com/user/theme

This generates go.mod. If you don't plan on letting others import your module, the exact name doesn't matter much, pick anything reasonable.

Declare the module you want to import in hugo.yaml:

module:
  imports:
    - path: github.com/user/theme

Running hugo to build the site automatically downloads the module, caches it, and generates go.sum to record its version and checksum.

When you import multiple modules at once, files with the same name merge according to UFS rules.

Common Commands Link to this heading

Here is a summary of commonly used hugo mod commands:

  • Update a single module:

    hugo mod get -u github.com/user/theme
  • Pin a specific version:

    hugo mod get -u github.com/user/theme@v0.42.0
  • Update all modules:

    hugo mod get -u
  • Update modules recursively:

    hugo mod get -u ./...
  • Clean up unused entries in go.mod/go.sum:

    hugo mod tidy
  • Clear the module cache:

    hugo mod clean

Vendor Link to this heading

hugo mod vendor is used for local inspection and temporary debugging.

This command copies all imported modules into the _vendor directory, which has higher priority, letting you modify and test modules directly within the _vendor directory. Directly modifying files inside _vendor is only for quick debugging. Running vendor again will overwrite those changes. The proper way to customize is still to override the same path in the project root through the UFS.

Replace Link to this heading

Use the replace directive to permanently swap out a dependency, for example when you switch to your own fork:

go.mod
require example.com/othermodule v0.1.0

replace example.com/othermodule => example.com/myfork/othermodule v0.1.0

Or point it to a local directory:

go.mod
replace github.com/user/theme => /home/user/projects/theme

Because replace lives in go.mod and ships with your project, it applies to everyone who builds this project, including CI.

Workspace Link to this heading

Use a workspace to configure modules for local development. Think of it as a temporary version of replace. For example, when you're developing a local module, you can point directly to your local files:

exampleSite/hugo.work
go 1.20

use .
use ../theme

Enable it temporarily with an environment variable:

HUGO_MODULE_WORKSPACE=hugo.work hugo server -s exampleSite

Or enable workspace mode long-term in hugo.yaml:

exampleSite/hugo.yaml
workspace: 'hugo.work'

The key difference between workspace and replace: workspace can be enabled temporarily and never gets written to go.mod. So when other people depend on your module, your replace settings won't affect them.

Practical Examples Link to this heading

Multilingual Sites Link to this heading

As described in Multilingual Sites, you can mount a given directory to a given site at a given location to complete multilingual configuration.

Shared Component Libraries Link to this heading

Multiple sites sharing the same set of shortcodes, partials, or CSS. Extract them into an independent module that every site can import:

[module]
  [[module.imports]]
    path = 'github.com/your-org/shared-components'

exampleSite Link to this heading

When building a theme, it's common to include an exampleSite/ directory inside the theme's repo as a demo site. But that exampleSite/ site depends on the theme sitting in the project's own root.

A workspace solves this cleanly:

exampleSite/hugo.work
go 1.20

use .
use ../
exampleSite/hugo.yaml
workspace: 'hugo.work'

node_modules Link to this heading

Mounts also work with node_modules, letting you mount its contents directly into a target directory without manually vendoring the package. Configure it like this:

module:
  mounts:
    - source: assets
      target: assets
    - source: node_modules/foo/
      target: assets/foo

You can then use the contents of that directory in your templates.

Separating Content From Source Code Link to this heading

Split content/ and assets/ into their own module, backed by an independent git repo, so writers never need to touch the theme's source code:

module:
  imports:
    - path: github.com/your-org/site-content

Writers only need access to the independent site-content repo, so they can't accidentally touch layouts/ or other code. Engineers only need to configure CI to download this module at build time, which cleanly separates responsibilities.

When local development requires seeing content changes in real time, you can mount it using the workspace approach mentioned earlier.

Separating Configuration Across Environments Link to this heading

Switch which data/ or params gets imported depending on whether you're building the production site or the preview site:

config/staging/hugo.yaml
module:
  imports:
    - path: config-staging
config/production/hugo.yaml
module:
  imports:
    - path: config-production

The config/staging and config/production directories switch automatically (external link) based on an environment variable, or manually set via hugo -e staging. With CI/CD, the configuration module to import switches based on the deployment environment.

Accessibility settings

Font size