A module is a unit of versioning and distribution: a tree of Go packages identified by a module path, with explicit dependencies and a recorded build list. Modules replaced the older GOPATH workflow in Go 1.11+ and have been the default since 1.16.

Anatomy of a module

my-module/
├── go.mod          // declares module path, Go version, dependencies
├── go.sum          // cryptographic hashes of dependencies (committed)
├── main.go
└── internal/...

A minimal go.mod:

module github.com/me/my-module

go 1.22

require (
    github.com/google/uuid v1.6.0
    golang.org/x/sync v0.7.0
)

The module path is what other code uses to import packages from your module:

import "github.com/me/my-module/internal/store"

Creating a module

mkdir my-module && cd my-module
go mod init github.com/me/my-module

The path doesn't have to exist on the network for local development, but if you want others to go get it, the path must resolve to your repo via the remote import path protocol (most commonly GitHub: github.com/<user>/<repo>).

Adding a dependency

Just import it, then run:

go mod tidy

tidy resolves all imports it finds in the source, adds them to go.mod, records hashes in go.sum, and removes any modules that are no longer used.

You can also pin versions explicitly:

go get github.com/google/uuid@v1.6.0
go get github.com/google/uuid@latest

Versioning rules (semver + MVS)

Modules use semantic versioning: vMAJOR.MINOR.PATCH.

  • Versions v0.x.y and v1.x.y live at the module path as-is.
  • Versions v2 and above require the module path to end in /v2 (or /v3, ...). This prevents accidental incompatible upgrades.
  • Go uses Minimum Version Selection (MVS): when multiple modules require different versions of the same dependency, Go picks the highest minimum. There is no SAT solver and no surprise upgrades.

replace and exclude directives

replace github.com/foo/bar => ../bar              // local fork
replace github.com/foo/bar v1.2.3 => github.com/me/bar v1.2.4-fix
exclude github.com/foo/bar v1.5.0                  // never select this version

Use replace for development; remove it before tagging a release.

internal/

Any package under a directory named internal/ is importable only by code rooted at the parent of that internal/. It's the official way to mark "private to this module".

github.com/me/m
└── internal
    └── secret      // importable from github.com/me/m/... only

Vendoring (optional)

go mod vendor

Copies all dependencies into a vendor/ directory and makes the build use them. Useful for hermetic builds or air-gapped environments.

Workspaces (go work)

If you're developing several modules together (e.g. a service plus a shared library you also own), use a workspace:

go work init ./service ./shared-lib

This creates go.work, telling Go to use the local checkout of each module listed even though they have separate go.mod files.

go.work is not committed in most projects (it's a per-developer convenience), but you can commit it for monorepos.

go.sum and integrity

go.sum records h1:-prefixed checksums for every module + version your build observes. The Go toolchain refuses a build if the sum doesn't match. Commit go.sum.

The default checksum database (sum.golang.org) verifies modules; you can disable or change it with GONOSUMCHECK / GOSUMDB.

Common workflows

go mod init <path>          # new module
go get <pkg>@<ver>          # add or upgrade a dependency
go get -u ./...             # upgrade all dependencies (minor/patch)
go get -u=patch ./...       # only patch upgrades
go mod tidy                 # sync go.mod / go.sum to imports
go mod why <pkg>            # explain why a module is needed
go mod graph                # dump dependency graph
go list -m all              # show effective build list
go list -m -u all           # show available updates

Publishing your module

  1. Push a Git repo at the module path.
  2. Tag a release: git tag v1.0.0 && git push --tags.
  3. Anyone can go get github.com/you/yourmod@v1.0.0.
  4. The Go module proxy (proxy.golang.org) caches it within minutes.

Run / inspect this repo's module

cd /Users/nelsonlai/sources/freelance/Go
cat go.mod
go list -m all
go test ./...