A replace directive in go.mod swaps the contents of a module, or one version of it, for code from somewhere else, such as your fork. It applies only in the main module. Libraries that depend on you ignore it, so the swap can quietly differ between your build and your consumers' builds.
What the Go reference says
The Go modules reference describes a replace directive as replacing the contents of a specific version of a module, or all versions, with contents found elsewhere. The right side can be another module path with a version, or a local file path. A few rules matter for forks:
- If a version appears on the left of the arrow, only that version is replaced. If it is omitted, all versions are replaced.
- With a local path on the right, the version is omitted and the directory must contain a go.mod file.
- If the replacement has a go.mod file, its module directive must match the module path it replaces.
- Replace directives apply only in the main module's go.mod and are ignored in other modules.
- A replace alone does not add a module to the graph. A require for the module must exist.
A typical fork replace
require example.com/lib v1.4.2
replace example.com/lib v1.4.2 => github.com/acme/lib v1.4.2-acme.1
Your code still imports example.com/lib. The build fetches your fork instead. Because the replacement's module directive must match the original path, your fork keeps the original's module identity inside its own go.mod.
What it fixes
You get your patched code without editing every import, and you can scope the swap to one version so it falls away when you move on. That makes replace a clean way to carry a single security patch, as described in carrying a patch while waiting for an upstream merge.
What it hides
| Hidden effect | Why it matters |
|---|---|
| Ignored by downstream modules | If you publish a library, your users get the unpatched original |
| Name stays the original | Tools that read go.mod may report the original and its advisories, not your fork's state |
| Version stays the original in the require line | A reader may think you run upstream 1.4.2 when you run a fork |
| Easy to forget | A replace added for a quick fix can live for years |
The second row cuts both ways. A scanner reading the require line may flag an advisory your fork already fixed, or miss one that exists only in your changes. See will scanners flag a renamed fork.
Habits that keep it honest
- Put a comment above every replace saying why, who owns it and when to remove it.
- Use a version specific left side where possible, so moving to a newer version drops the swap.
- Review replaces in code review like any dependency change.
- List active replaces in your fork register and review them each sync.
- If you publish a library, do not rely on replace to ship a fix. Release a fixed version instead.
Where Safeguard fits
Safeguard's dependency scanning and policy gates can flag changes to dependency manifests in a pull request, which is where a new replace shows up. Try a free scan at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen. Watch how a change in a pull request is checked before merge.
Quick check
Does my replace protect people who import my library?
No. The Go reference says replace directives apply only in the main module and are ignored in other modules.
Can a replace add a new dependency?
No. A replace alone does not add a module to the graph. A require for it must also exist.
Sources
Last reviewed: 19 October 2026.