Sitemap
Sylvain Tiset

Sylvain Tiset

Tech Lead developer

Go Modules & Workspaces — The Complete Guide (go mod, go work, vendor, tidy & more)

12 min readJul 1, 2026

--

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.

Press enter or click to view image in full size
Generated by AI

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/myapp

This 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.22

In 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:

  • module defines the import path of the project. This is how other modules reference it.
  • go indicates the Go version the module was written for. It also influences certain toolchain behaviors.
  • require lists 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 // indirect

This 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.22

It 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.3

Key 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.3

Key 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.0

The 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 version
  • GOTOOLCHAIN → 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.mod file

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:

  1. An import is added in the code
    A package is referenced using its module path inside a Go file.
  2. The Go toolchain resolves the dependency
    The required module and version are determined automatically.
  3. go.mod is updated
    The dependency is added (or adjusted) in the require section.
  4. go.sum is updated
    Checksums are recorded to guarantee integrity and reproducibility.
  5. The module is downloaded into the cache
    Dependencies are stored in the local module cache ($GOMODCACHE), avoiding repeated downloads.
  6. (Optional) Dependencies are vendored
    Running go mod vendor copies dependencies into the vendor/ directory for reproducible or offline builds.

This process is mostly automatic and happens during commands like go build, go run, or go get.

Press enter or click to view image in full size
Generated by AI

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.mod but are no longer needed
  • Updates go.sum to 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 tidy analyzes 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@latest

Key points:

  • Adds a new dependency or updates an existing one to a specified version
  • Automatically updates go.mod and go.sum to 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 vendor

Key points:

  • Copies all required dependencies into the /vendor folder
  • 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=vendor

The 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=vendor

Key aspects:

  • Overrides the default behavior of fetching dependencies from the module cache
  • Guarantees reproducible builds by relying solely on the contents of the vendor folder
  • 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 ./serviceB

This 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.mod files 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 tidy is 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 vendor

Key points:

  • Aggregates dependencies from all modules in the workspace into a single /vendor folder
  • 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 download

go 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 verify

go 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 all

These 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 -modcache

Key points:

  • Deletes all downloaded modules from the local cache
  • Useful for troubleshooting corrupted or inconsistent dependencies
  • Does not affect the vendor directory or the go.mod and go.sum files

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/bar

Key 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 replace directive

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.3

Key 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 tidy may remove dependencies that are still required in tests or optional builds if they are not explicitly imported.
  • Confusion between vendor and cache: The /vendor directory 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 sync aligns module dependencies across a workspace but does not remove unused modules. Forgetting to run go mod tidy can leave stale entries.
  • Neglecting go.sum: Failing to commit go.sum can result in non-reproducible builds across different environments.
  • Improper use of replace: Using replace locally 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 tidy

Multi-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 module
  • go get: Add or update a dependency
  • go mod tidy: Clean and synchronize dependencies
  • go mod vendor: Copy dependencies into a local vendor directory
  • go work init: Initialize a workspace with multiple modules
  • go work sync: Synchronize dependencies across all modules in a workspace
  • go work vendor: Vendor dependencies for the entire workspace
  • go mod download: Pre-fetch all module dependencies
  • go mod verify: Verify module checksums against go.sum
  • go list -m all: List all modules and their versions
  • go clean -modcache: Clear the local module cache
  • replace: 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.

--