How to Use replace in go.mod for Local Development

Use the replace directive in go.mod to point a module path to a local directory for development testing.

The local override problem

You are building a web server that depends on a logging library. You found a bug in that library. You fixed it in your fork, but the web server still pulls the old version from the internet. You need a way to tell Go, "Ignore the remote version. Use the code sitting in this folder on my laptop."

Publishing a new version takes time. Waiting for a maintainer to merge your fix takes longer. You want to test the change immediately in the context of your application. The replace directive in go.mod solves this by redirecting module resolution to a local path or a different version before Go attempts to download anything.

How replace works

The replace directive modifies how the Go toolchain resolves module paths. It does not copy files. It does not change the import statements in your code. It changes the source of truth during the build process.

When Go reads your go.mod file, it collects all require directives. Before it checks the module proxy or your local cache, it applies every replace rule. If a replace matches a required module, Go swaps the path and version for the target you specified. The rest of the build proceeds as if the dependency always lived at that new location.

This mechanism supports three distinct workflows. You can point a module to a local directory on your filesystem. You can point a module to a specific version that differs from the require line. You can point a module to a completely different module path, effectively remapping imports.

Minimal example

Here is the simplest replace directive: spawn one, point it to a local folder, and let Go use that code.

// go.mod
module example.com/myapp

go 1.22

// Declare the dependency as usual
require example.com/some/lib v1.0.0

// Redirect the module to a local folder relative to go.mod
replace example.com/some/lib => ./libs/some-lib

The replace line takes the form replace old => new. The old side must match a module path that appears in your require block or in the transitive dependency tree. The new side can be a local path starting with ./ or ../, or it can be a module path with a version.

Walkthrough of resolution

Understanding the order of operations prevents confusion when things break. The Go toolchain follows a strict sequence when resolving modules.

First, the toolchain parses go.mod and builds a graph of all required modules. It includes direct dependencies and every transitive dependency pulled in by those modules. Second, it applies replace directives. Every replace rule is checked against the graph. If a match is found, the node in the graph is updated to point to the replacement. Third, version selection occurs. If the replacement is a local path, Go reads the go.mod file inside that directory to determine the version. If the replacement is a version, Go uses that version. Fourth, the toolchain fetches the modules. For local paths, it reads the files directly. For remote paths, it queries the proxy or git.

This order means replace can affect transitive dependencies. If module A requires module B, and module B requires module C, a replace for module C will apply to the dependency coming from B. You do not need to add replace directives for every module in the tree. One replace covers all references to that module path.

The compiler rejects the build with module has no go.mod file if you point a replace to a local directory that lacks a module definition. Every module, even a local development copy, needs a go.mod file declaring the module path. The path inside the local go.mod must match the old side of the replace directive, or Go stops with module path mismatch.

Realistic example

Here is a go.mod file handling multiple local overrides and a version pin, demonstrating how replaces coexist with standard requirements.

// go.mod
module example.com/myapp

go 1.22

require (
    // Standard dependencies
    example.com/shared/auth v0.5.0
    example.com/shared/db v1.2.0
    example.com/external/httpclient v2.1.0
)

// Override auth to use local development copy
replace example.com/shared/auth => ../shared/auth

// Override db to use a pre-release version for testing
replace example.com/shared/db => example.com/shared/db v1.2.0-rc1

// Remap external client to an internal fork with a different path
replace example.com/external/httpclient => example.com/internal/httpclient v2.1.0

The first replace points to a sibling directory. This is common when you are developing a library and a consumer app in adjacent folders. The second replace pins the database driver to a release candidate. This lets you test a future version without changing the require line, which might be locked by a CI policy. The third replace changes the module path entirely. This is useful when you have forked a project and renamed the module to avoid confusion, but your code still imports the original path.

The community treats replace directives as local development artifacts. You should never commit a replace line to a shared repository. It breaks builds for anyone else who does not have the local folder or the specific version. Use go mod edit to manage replaces programmatically, and remember to remove them before pushing code. The command go mod edit -dropreplace=example.com/some/lib removes a replace cleanly.

Pitfalls and errors

Replace directives introduce subtle failure modes. Knowing these patterns saves debugging time.

A common mistake is replacing a module with a directory that lacks a go.mod file. The compiler rejects this with module has no go.mod file. Every module, even a local one, needs a module definition. Create a go.mod in the target directory with go mod init example.com/some/lib.

Another trap is the path mismatch. If your local directory declares module example.com/fork but you replace example.com/original, Go stops with module path mismatch. The paths must align unless you are intentionally remapping the entire import tree. If you are remapping, ensure the new side of the replace includes the correct path.

Transitive dependencies can cause confusion. If module A requires module B, and module B requires module C, replacing module B does not automatically replace module C. If you also need to replace module C, you must add a separate replace directive for it. The replace system does not inherit replacements through the dependency graph.

The go mod tidy command interacts with replaces in a specific way. tidy removes unused replace directives. If you have a replace for a module that is not required directly or transitively, tidy drops it. This is a safety feature that keeps go.mod clean. It also means you cannot use replace to preload a module that your code does not import. The module must be used somewhere for the replace to stick.

Version conflicts can arise when the replacement module has incompatible dependencies. If the local version of a library requires a newer version of a transitive dependency than the rest of the graph, Go attempts to unify the versions. If it cannot find a version that satisfies all constraints, the build fails with a minimal version selection error. The error message lists the conflicting modules and versions. Resolving this usually requires updating the require block or adjusting the local module's dependencies.

Decision matrix

Choose the right tool based on your development workflow and repository structure.

Use a replace directive with a local path when you are iterating on a dependency and need to test changes in a consumer module without publishing.

Use a replace directive with a version when you need to pin a dependency to a pre-release or patch version that isn't the latest semver tag.

Use a replace directive with a different module path when you are forking a module and changing its import path entirely.

Use Go workspaces when you are developing multiple modules in the same repository and need them to reference each other locally.

Use go mod vendor when you need a fully reproducible build environment that doesn't rely on network access or local overrides.

Replace is a redirect, not a copy. Keep your go.mod clean and revert replaces before sharing code.

Where to go next