Initialise a Hugo Project as a Module Without Guesswork

hugo mod init turns an ordinary Hugo project into a Go module, and the module path you choose here sticks. It normally creates or updates project metadata only; it does not download a theme or pull in a dependency by itself. Allow about ten minutes, including a quick backup and verification.

This guide uses Hugo 0.123.7, installed from the Ubuntu package on this machine. Hugo hands module initialisation off to the installed Go toolchain, so the exact go line in go.mod can vary with that toolchain. Every command below is an ordinary user command: none of them need sudo.

1. Check the project and tool versions

Run the command from the project root, where the Hugo configuration and content directories live. If you are not sure which directory that is, stop and find the one containing hugo.yaml, hugo.toml or hugo.json.

$ pwd
/home/you/sites/example
$ command -v hugo
/usr/bin/hugo
$ hugo version
hugo v0.123.7+extended linux/amd64 BuildDate=2026-03-17T19:51:14Z VendorInfo=ubuntu:0.123.7-1ubuntu0.3+esm2
$ go version
go version go1.26.1 linux/amd64

Your paths and Go version will differ. What matters is that both commands resolve to the tools you actually intend to use. Hugo's man page describes hugo mod init as initialising the current project, with the syntax hugo mod init [flags] [args].

2. Inspect existing module metadata

Initialisation changes project state, so check whether a module file already exists before you run it:

$ test -f go.mod && echo 'go.mod already exists' || echo 'no go.mod found'
no go.mod found
$ git status --short

An empty status means the working tree was clean in this example. If go.mod already exists, read it and check its module line rather than blindly initialising again:

$ sed -n '1,20p' go.mod
module example.com/site

go 1.22

Do not overwrite a module file that carries imports, replacements or version constraints without first understanding why they are there. Save a copy before changing an established project:

$ cp --preserve=all go.mod go.mod.before-hugo-mod-init

The backup is a recovery aid, not a reason to ignore a dirty tree. Keep it until the project builds successfully.

3. Choose the module path

Hugo tries to guess the module path, but a local directory often gives it nothing reliable to work with. Pass an explicit path when the project will be imported by another module, or when you want the result stable across machines.

A path conventionally resembles a repository location:

$ hugo mod init github.com/your-account/example-site
go: creating new go.mod: module github.com/your-account/example-site

Use your real repository path in place of github.com/your-account/example-site. The path is an identifier, not proof the code lives there. For a private or purely local project, a name such as example.test/site is also valid for local use.

Skip the protocol prefix: use github.com/your-account/example-site, not https://github.com/your-account/example-site. If the project is a module inside a larger repository, include the subdirectory in the path if that is how consumers will identify it.

4. Verify the generated file

Read the new file and check its first lines:

$ sed -n '1,12p' go.mod
module github.com/your-account/example-site

go 1.26.1

The module line should match the path you chose. The Go version is written by whichever Go executable Hugo invoked; do not edit it just to make the output resemble another machine, since a different version can affect later dependency operations. Record it in your project notes if reproducibility matters.

Now ask Hugo to show the module-related configuration it can read:

$ hugo config | grep -E '^(module|theme)' || true

This is a light sanity check, not a dependency download. A project can be a module with no imports or theme configured yet.

5. Add imports only when you need them

Initialising the project does not add a theme. Imports belong in the Hugo configuration, for example:

module:
  imports:
    - path: github.com/your-account/example-theme

Keep the import path separate from the module path in go.mod. After adding an import, use whichever Hugo module command fits, such as hugo mod get or hugo mod tidy, and review any resulting go.sum changes. Those operations can reach out to version-control or module services and pull code straight into the build, so review the source and version before anything imported goes near production.

If all you need is a local theme or component, a local import path or a replace directive can work better. Keep local paths out of a published module unless every consumer can reach the same directory.

6. Build before committing

Run a normal build from the same project root:

$ hugo --quiet
$ printf 'build status: %s\n' "$?"
build status: 0

Hugo may create a build lock or write generated output depending on your project configuration. Check the resulting status:

$ git status --short
 M go.mod

The exact list depends on your repository and output settings. Review every changed file. Do not commit a generated public directory just because it turned up during the check, and do not discard a meaningful change without finding out why it appeared.

7. Recover from a bad or repeated initialisation

If the command fails while guessing the path, rerun it with an explicit module path. A failure from the underlying Go command can look like this:

$ hugo mod init
Error: failed to init modules: failed to execute 'go [mod init ]': ... cannot determine module path ...

This means the directory did not give Go enough to infer a name from. It does not mean Hugo needs elevated privileges; supply the path from step 3 instead.

If you picked the wrong path and nothing else has touched go.mod, restore the backup or edit only the module line after checking the repository history. For the backup made above:

$ cp --preserve=all go.mod.before-hugo-mod-init go.mod
$ sed -n '1,5p' go.mod

Warning: this overwrites the current go.mod, so use it only when you know that backup is the correct previous file. If the file has since picked up dependencies or replacements, stop and recover it from version control with your normal review process instead. Do not delete go.sum or run a broad clean-up command as a guess.

Done means