Packages, modules and tooling · 3 MIN READ
A module is a release boundary; a package is a directory
Resolve module roots, import paths, internal visibility and major-version suffixes.
A repository, a module and a package can be three different boundaries. Trace the module path plus the package subdirectory before changing imports. A version suffix is part of the import identity; a folder name alone does not decide which source the compiler builds.
Packages and production tooling · A MAP OF THE IDEA
- Repository
- go.mod root
- Package import
- Versioned API
Useful when
- Resolve module roots, import paths, internal visibility and major-version suffixes.
Watch out
- A module versions a set of packages together. A package groups source in one directory; the repository name does not override its declared package identifier.
Implementation & operation cost: Each extra module adds versioning, publishing and consumer-compatibility work; package boundaries inside one module are cheaper.
Locate the source that is actually selected
Start from the working directory and inspect the active module and workspace. A nested go.mod starts another module and can make ./... skip source you expected to test. Package names need not match the final path element, although a clear correspondence usually makes code easier to read.
go env GOMOD GOWORK
go list -f '{{.ImportPath}} => {{.Dir}}' ./...
go list -mGOMOD identifies the active module file. The package listing maps each import to a real directory, so an unexpected checkout or nested module becomes visible before you edit source.
Major versions have distinct identities
For ordinary module-aware releases at v2 and above, /v2 is part of the module and import path. Updating a version number without updating the path is not a compatible major upgrade. Some legacy modules use +incompatible; do not copy that exception into a new module design.
module example.com/catalog/v2
go 1.24.0The root package is imported as example.com/catalog/v2; a subpackage is example.com/catalog/v2/search. The file illustrates path structure rather than asking you to fetch a nonexistent example module.
Keep ownership inside one dependency direction
An internal directory limits which importers may reach a package; it is not an authorization boundary for running code. A package import cycle fails before the program starts. Move a small consumer-owned contract or shared value to an acyclic boundary instead of solving a cycle with init side effects.
TAKE THIS WITH YOU
Identify the active module and actual package directory first. Treat a new major import path as an API migration, and keep the dependency graph acyclic.
Check your understanding
How do a module and a package relate?
Go to the source
Progress stays in this browser. No account needed.