Working with Hugo Module Locally

Hugo Module developement requires to be able to work locally. This walkthrough will cover how to setup a Module without a repo, import it in a project and start developing it!

Hugo Module developement requires to be able to work locally. No one wants to push their changes to a repo, in order for another Hugo project to imports those changes.

In this note we’ll cover how to approach Hugo Module local developement without a repo setup.

We’ll use two Hugo projects throughout this walkthrough.

  • A Hugo Module on one hand, which we want to edit locally.
  • A Hugo Website on the other hand which should import our local module.

Init the local Hugo Module.

Let’s pretend our imaginary repo will live at

As the repo does not exist yet hugo mod init won’t be able to validate the repo.
We’ll have to create a go.mod file ourselves. It should look like this:


go 1.14

Importing the local Module

Good. Now let’s switch to our Hugo Website, the Hugo project which will import our local Hugo Module.

We’ll start by adding the local Module to our list of imports:

# config.yaml

    - path:

Don’t run hugo yet!

Now we’ll be working on the Hugo Website’s own go.mod file, not the Module’s.

We know that if we run hugo while importing a Module with a proper repo on GitHub or else, Hugo will add a require directive to our project’s go.mod file. This directive would look like this:

require v0.0.0-somegiberish // indirect

Or with multiple imports:

require ( v0.0.0-somegiberish // indirect v0.1.1 // indirect

Ok, we’ll assume our Hugo Website already sports a go.mod file pointing to its own repo.
Adding the require statement, it should now look like this:


go 1.14

require v0.0.0-somegiberish // indirect

Don’t run hugo yet!

Almost there, here comes the real deal!, the real trick: in order to tell Hugo to look for our local files instead of the distant (inexisting!) repo, we’ll add one innocent line to the Hugo Website’s go.mod file.


// Innocent line below!
replace => /Users/someone/dir/hugo-module

go 1.14

require v0.0.0-somegiberish // indirect

The replace directive, positioned before the require one, will make sure Hugo picks the files straight from your machine, rather than the repo’s. It is fully integrated with Hugo Serve, so any file updated in our local Hugo Module will trigger a synch of the Hugo Website.

Ok! Now you can run hugo or hugo serve and start playing around with your local Hugo Module!

Final notes

Whenever you need to comment out the replace statement, go uses //:

// replace => /Users/someone/dir/hugo-module

Also, just like require covered above, the replace directive can take multiple “repos”:

replace ( => /Users/someone/dir/hugo-module => /Users/someone/dir/hugo-emojis

Of course, once your files are up in the cloud, GitHub or otherwise, you should remove the require directive (the one ending with v0.0.0-giberish) and let hugo writes its own require with the proper versioning suffix.


When importing a Hugo Module, your project (which is also a Module…) will look for the latest release or if none is found, the latest commit on the default branch.

You should really stick with release in order to control what feature is ready for distribution.

In order for Hugo to do the version upgrade based on release, your Module should use proper semantic versioning as the release’s tag version.

A note about v2.0.0.

Hugo Module is powered by Go Modules of which it follows many rule. One is that any release equal or above to v2.0.0 can introduce breaking changes. As a result, the Go Module logic will not let you upgrade a Module from a < v2 to a ≥ v2. You should read this piece to address that limitation: