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/themeThis 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/themeRunning 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/themePin a specific version:
hugo mod get -u github.com/user/theme@v0.42.0Update all modules:
hugo mod get -uUpdate modules recursively:
hugo mod get -u ./...Clean up unused entries in
go.mod/go.sum:hugo mod tidyClear 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:
require example.com/othermodule v0.1.0
replace example.com/othermodule => example.com/myfork/othermodule v0.1.0Or point it to a local directory:
replace github.com/user/theme => /home/user/projects/themeBecause 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:
go 1.20
use .
use ../themeEnable it temporarily with an environment variable:
HUGO_MODULE_WORKSPACE=hugo.work hugo server -s exampleSiteOr enable workspace mode long-term in 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:
go 1.20
use .
use ../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/fooYou 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-contentWriters 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:
module:
imports:
- path: config-stagingmodule:
imports:
- path: config-productionThe 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.