Go Modules & Workspaces — The Complete Guide (go mod, go work, vendor, tidy & more)
Go dependency management has evolved from GOPATH to modules and now workspaces, bringing powerful tools — but also confusion. Commands like go mod tidy, go get, or go work sync often feel unclear and overlapping. This article cuts through the noise to provide a simple, practical mental model for understanding how everything fits together.
What Is a Go Module?
A Go module is the fundamental unit of dependency management in Go. It represents a versioned collection of packages that are developed and released together. Every module is defined by a go.mod file located at its root.
A module is identified by a unique path, typically matching a repository location:
go mod init github.com/myorg/myappThis command creates a go.mod file, which acts as the source of truth for the module’s dependencies and metadata.
A minimal example looks like this:
module github.com/myorg/myapp
go 1.22In practice, a module isolates a project from the rest of the system. Dependencies are no longer tied to a global workspace (as in the GOPATH era), but instead are explicitly declared, versioned, and reproducible.
This shift is what enables reliable builds, clear dependency graphs, and modern tooling in the Go ecosystem.
Understanding go.mod
The go.mod file is the central piece of a Go module. It defines the module’s identity, its dependencies, and a few rules that control how those dependencies are resolved.
A typical file looks like this:
module github.com/myorg/myapp
go 1.22
require (
github.com/gin-gonic/gin v1.9.0
github.com/some/lib v1.2.3 // indirect
)Each directive has a clear responsibility:
moduledefines the import path of the project. This is how other modules reference it.goindicates the Go version the module was written for. It also influences certain toolchain behaviors.requirelists the dependencies and their exact versions.
Dependencies can be direct or indirect:
- A direct dependency is explicitly imported in the code
- An indirect dependency is required by another dependency
Indirect dependencies are marked automatically:
github.com/some/lib v1.2.3 // indirectThis distinction is important because it explains why some dependencies appear even if they are not directly used in the code.
The go.mod file may also contain more advanced directives such as replace and exclude, which override or restrict dependency resolution. These are typically used in more complex setups, such as local development or when dealing with problematic versions.
Overall, go.mod acts as a precise and reproducible contract: it describes exactly what is needed to build the module, and ensures that the same versions are used everywhere.
Controlling Go Versions — go vs toolchain vs GOTOOLCHAIN
Go provides several mechanisms to control which version of the toolchain is used to build a project. These mechanisms operate at different levels but work together to ensure consistent and reproducible builds.
The go Directive — Minimum Version
The go directive, defined in go.mod, specifies the minimum Go version required by the module:
go 1.22It determines:
- The minimum version of Go expected to build the module
- Certain language features and toolchain behaviors
It does not enforce the exact version used, only compatibility.
The toolchain Directive — Preferred Version
The toolchain directive allows specifying a preferred Go toolchain version:
toolchain go1.22.3Key points:
- Suggests the exact Go version that should be used
- If the required toolchain is not installed, it can be automatically downloaded
- Helps ensure consistency across different environments
This directive complements the go version by making the expected toolchain more explicit.
GOTOOLCHAIN — Environment Override
The GOTOOLCHAIN environment variable provides a way to override toolchain selection at runtime:
GOTOOLCHAIN=go1.22.3Key points:
- Forces the use of a specific Go version regardless of
go.mod - Useful in CI pipelines or controlled environments
- Can be used to test builds against different Go versions
One more thing: when running:
go version // can answer 1.22.3
GOTOOLCHAIN=go1.21.0 go version // will answer 1.21.0The version displayed is the Go toolchain currently being executed, not necessarily the one defined in a project.
These three mechanisms can be understood as layers:
go→ defines compatibility (minimum version)toolchain→ defines a preferred versionGOTOOLCHAIN→ provides an explicit override
Together, they offer flexible and predictable control over how Go projects are built across different machines and environments.
What Is go.sum?
The go.sum file complements go.mod by ensuring the integrity of dependencies. While go.mod defines which versions are required, go.sum guarantees that the exact same code is used every time.
Each entry in go.sum is a cryptographic checksum associated with a specific module version. When a dependency is downloaded, its content is verified against this checksum. If anything differs, the build fails.
A simplified example:
github.com/gin-gonic/gin v1.9.0 h1:abcdef123456...
github.com/gin-gonic/gin v1.9.0/go.mod h1:123456abcdef...Two things are recorded:
- The checksum of the module’s source code
- The checksum of its
go.modfile
This mechanism protects against:
- Tampered dependencies
- Corrupted downloads
- Unexpected changes in upstream code
The file is automatically managed by the Go toolchain, especially when running commands like go mod tidy or go get. It should always be committed to version control to ensure reproducible builds across environments.
In short, if go.mod defines the dependency graph, go.sum secures it.
Dependency Lifecycle
Understanding how dependencies flow through a Go project helps make every command feel predictable. Instead of memorizing tools, it becomes easier to see how they fit into a single lifecycle.
A typical sequence looks like this:
- An import is added in the code
A package is referenced using its module path inside a Go file. - The Go toolchain resolves the dependency
The required module and version are determined automatically. go.modis updated
The dependency is added (or adjusted) in therequiresection.go.sumis updated
Checksums are recorded to guarantee integrity and reproducibility.- The module is downloaded into the cache
Dependencies are stored in the local module cache ($GOMODCACHE), avoiding repeated downloads. - (Optional) Dependencies are vendored
Runninggo mod vendorcopies dependencies into thevendor/directory for reproducible or offline builds.
This process is mostly automatic and happens during commands like go build, go run, or go get.
Over time, the dependency graph can become slightly out of sync with the code (for example, after removing imports). This is where commands like go mod tidy come in.
go mod tidy — Keeping Dependencies in Sync
The go mod tidy command ensures that a module’s go.mod and go.sum files accurately reflect the code. It acts as a maintenance tool, adding missing dependencies and removing those no longer used.
Key behaviors:
- Adds missing dependencies that are imported in the code but not yet listed in
go.mod - Removes unused dependencies that appear in
go.modbut are no longer needed - Updates
go.sumto match the resulting dependency graph
It is particularly important to run go mod tidy before committing code, as it guarantees that the module files are minimal, correct, and reproducible.
An important detail:
go mod tidyanalyzes all packages in the module, including test files, so dependencies used only in tests are also maintained correctly.
go get — Adding or Updating Dependencies
The go get command explicitly modifies a module’s dependencies by adding new packages or updating existing ones. Unlike go mod tidy, which cleans and synchronizes dependencies automatically, go get is used intentionally to change what the module requires.
Example usage:
go get github.com/sirupsen/logrus@latestKey points:
- Adds a new dependency or updates an existing one to a specified version
- Automatically updates
go.modandgo.sumto reflect the change - Does not remove unused dependencies (that remains the role of
go mod tidy)
go get vs go mod tidy
While both commands interact with go.mod and go.sum, their purposes are distinct:
go getExplicitly add or update a dependency to a specific version
go mod tidyClean and synchronize dependencies, adding missing ones and removing unused ones
In other words, go get is intentional (used to change the dependency graph) whereas go mod tidy is corrective, ensuring that the module files accurately reflect the current codebase. Together, they maintain a reliable and reproducible set of dependencies.
go mod vendor — Copying Dependencies Locally
The go mod vendor command creates a local vendor directory containing all dependencies required by a module. This allows builds to rely on local copies rather than downloading from the module cache or external sources.
go mod vendorKey points:
- Copies all required dependencies into the
/vendorfolder - Ensures reproducible and offline builds
- Useful in environments where external network access is restricted
To build using the vendored dependencies, the following command can be used:
go build -mod=vendorThe vendor directory provides a snapshot of the dependency graph at a specific point in time, independent of the module cache, and can be committed to version control when necessary. This guarantees that builds remain consistent even if upstream modules change or become unavailable.
Vendor Mode — Using Local Dependencies
Vendor mode instructs the Go toolchain to prioritize the local vendor directory over the module cache when building or running a project. This ensures that only the vendored copies of dependencies are used.
Get Sylvain Tiset’s stories in your inbox
Join Medium for free to get updates from this writer.
To enable vendor mode during a build:
go build -mod=vendorKey aspects:
- Overrides the default behavior of fetching dependencies from the module cache
- Guarantees reproducible builds by relying solely on the contents of the
vendorfolder - Particularly useful for CI pipelines, corporate environments, or offline builds
Vendor mode provides full control over which versions of dependencies are actually used, independent of external sources, while still allowing normal module operations outside of vendor mode.
Workspaces (go.work) — Managing Multiple Modules
Go workspaces enable the management of multiple modules as a single development environment. A workspace is defined by a go.work file, which lists all modules included in the workspace.
To create a workspace:
go work init ./serviceA ./serviceBThis command generates a go.work file:
go 1.22
use (
./serviceA
./serviceB
)Key points:
- Simplifies development across multiple modules in the same repository (monorepos)
- Allows changes in one module to be immediately visible to others in the workspace
- Keeps module-level
go.modfiles intact while providing a unified view
Workspaces make multi-module development predictable and efficient, reducing the need for temporary replace directives when working locally.
go work sync — Synchronizing Dependencies Across Modules
The go work sync command updates the go.mod files of all modules within a workspace to align with the workspace configuration. It ensures that shared dependencies and module versions remain consistent across modules.
Key behaviors:
- Propagates workspace-level dependency decisions into each module’s
go.mod - Keeps versions synchronized when multiple modules depend on the same packages
- Does not remove unused dependencies;
go mod tidyis still required for cleaning
In essence, go work sync maintains consistency across a workspace, making multi-module development smoother and more predictable.
go work vendor — Vendoring at the Workspace Level
The go work vendor command creates a vendor directory that includes all dependencies required by every module in the workspace. This extends the concept of vendoring from a single module to a multi-module setup.
go work vendorKey points:
- Aggregates dependencies from all modules in the workspace into a single
/vendorfolder - Enables reproducible and offline builds for the entire workspace
- Useful in monorepos where multiple modules share dependencies
Using go work vendor ensures that builds are consistent across the workspace and simplifies dependency management when working in environments without network access.
Other Essential Commands
Several additional commands support dependency management and help maintain module integrity:
go mod download — Pre-fetch Dependencies
Downloads all required modules to the local module cache without building them. This is useful for caching dependencies in CI pipelines or preparing an environment for offline work.
go mod downloadgo mod verify — Ensure Integrity
Checks that all downloaded modules match the checksums recorded in go.sum. Any tampering or corruption causes the command to fail.
go mod verifygo list -m all — Inspect Dependencies
Lists all modules used in the build, including their versions. This command provides a complete view of the current dependency graph.
go list -m allThese commands complement go mod tidy and go get by enabling verification, inspection, and preparation of module dependencies in a controlled and reproducible way.
Cleaning the Cache — go clean -modcache
The Go toolchain stores downloaded modules in a local cache ($GOMODCACHE) to avoid repeated downloads. Occasionally, the cache may become corrupted or outdated, and clearing it can resolve build issues.
To remove all cached modules:
go clean -modcacheKey points:
- Deletes all downloaded modules from the local cache
- Useful for troubleshooting corrupted or inconsistent dependencies
- Does not affect the
vendordirectory or thego.modandgo.sumfiles
Cleaning the module cache ensures that future builds fetch fresh copies of dependencies, restoring a consistent and reliable environment.
Advanced: replace, exclude & retract
Go provides several directives to control how dependencies are resolved. While they may look similar, they serve different purposes and operate at different levels.
replace — Override a Dependency
The replace directive points a module requirement to a different location or version. This is commonly used to test local changes or work around upstream issues.
replace github.com/foo/bar => ../local/barKey points:
- Overrides the source of a dependency with a local path or a different version
- Useful for local development and testing changes before they are released
- Affects only the module containing the
replacedirective
exclude — Prevent a Specific Version
The exclude directive forbids the use of a particular module version. This is helpful when a version is known to be broken or incompatible.
exclude github.com/foo/bar v1.2.3Key points:
- Blocks specific versions from being used
- Ensures that the build does not accidentally use a problematic release
retract — Mark Versions as Invalid
The retract directive is used by module authors to indicate that a published version should no longer be used:
retract v1.2.3
retract [v1.2.0, v1.2.3] // for a range- Signals that a version is broken, insecure, or released by mistake
- Does not delete the version, but warns users and tooling
- Used in the module that published the problematic version
Together, these directives provide fine-grained control over dependency resolution in both development and release scenarios.
go.work vs replace
Both go.work and the replace directive can influence dependency resolution, but they operate at different levels and serve different purposes.
In essence, go.work is designed to coordinate dependencies across several modules in a workspace, while replace modifies the dependency resolution for a single module. Together, they provide complementary tools for managing complex development setups.
Common Pitfalls
Several recurring issues can cause confusion when working with Go modules and workspaces:
- Unintended removal of dependencies: Running
go mod tidymay remove dependencies that are still required in tests or optional builds if they are not explicitly imported. - Confusion between vendor and cache: The
/vendordirectory and the module cache serve different purposes. Relying on one while expecting behavior from the other can lead to inconsistent builds. - Misunderstanding workspace synchronization:
go work syncaligns module dependencies across a workspace but does not remove unused modules. Forgetting to rungo mod tidycan leave stale entries. - Neglecting
go.sum: Failing to commitgo.sumcan result in non-reproducible builds across different environments. - Improper use of
replace: Usingreplacelocally without proper consideration can create discrepancies for other developers or CI pipelines.
Awareness of these pitfalls helps maintain reliable and predictable dependency management throughout development.
Recommended Workflows
Establishing consistent workflows ensures that Go modules and workspaces remain accurate and maintainable. Recommended approaches differ for single-module and multi-module projects.
Single-Module Workflow
- Add or update a dependency intentionally:
go get <dependency>- Synchronize the module files to match the current code:
go mod tidyMulti-Module (Workspace) Workflow
- Initialize a workspace including all relevant modules:
go work init ./moduleA ./moduleB- Synchronize dependencies across the workspace:
go work sync- Clean and align each module’s dependencies:
go mod tidy ./...Following these steps ensures that dependencies are consistent, reproducible, and correctly reflected in both go.mod and go.sum, regardless of project complexity.
Ultimate Cheat Sheet
go mod init: Create a new modulego get: Add or update a dependencygo mod tidy: Clean and synchronize dependenciesgo mod vendor: Copy dependencies into a localvendordirectorygo work init: Initialize a workspace with multiple modulesgo work sync: Synchronize dependencies across all modules in a workspacego work vendor: Vendor dependencies for the entire workspacego mod download: Pre-fetch all module dependenciesgo mod verify: Verify module checksums againstgo.sumgo list -m all: List all modules and their versionsgo clean -modcache: Clear the local module cachereplace: Override a dependency version or path (module directive)exclude: Prevent a specific version from being used (module directive)retract: Mark a version as invalid or withdrawn (module author directive)
Hoping go Module & Workspace commands have no more secrets to you. If you liked this article, feel free to clap it, comment it or share it. To stay updated with my content, you can follow me on Medium.




