go work: Workspace Mode for Multi-Module Repos
go.work lets you build several modules together using local source instead of published versions.
It is the supported way to develop multi-module monorepos without sprinkling temporary replace directives in every go.mod.
Summary
Workspace mode adds a go.work file listing module directories with use directives.
When GOWORK points at that file (auto-detected in parent directories), the toolchain resolves those modules to local paths.
Your individual go.mod files stay publishable; workspace config is typically local or committed only for repo-wide dev orchestration.
Recipe
Quick-reference recipe card - copy-paste ready.
mkdir -p repo/{app,lib}
cd repo/lib && go mod init example.com/lib
cd ../app && go mod init example.com/app
cd ..
go work init ./app ./lib
go work use ./app ./libcd app
go run .When to reach for this:
- Editing library and service modules side by side in one clone.
- Avoiding committed
replace => ../liblines that break on different machine layouts. - Running CI that builds multiple modules with consistent local versions.
Working Example
repo/
go.work
app/
go.mod
main.go
lib/
go.mod
greet.go
// lib/greet.go
package lib
func Greet() string { return "hello from lib" }// lib/go.mod
module example.com/lib
go 1.26// app/go.mod
module example.com/app
go 1.26
require example.com/lib v0.0.0// app/main.go
package main
import (
"fmt"
"example.com/lib"
)
func main() {
fmt.Println(lib.Greet())
}// go.work
go 1.26
use (
./app
./lib
)cd app
go run .What this demonstrates:
apprequiresexample.com/libwithout a pseudo-version or replace ingo.mod.go.worktells the toolchain to use./libon disk.- Publishing
appalone still records a properrequirefor consumers.
Deep Dive
How It Works
go work initcreatesgo.workat the repo root (or chosen path).useentries list module roots relative to the work file.- Builds inside member modules prefer local sources for listed paths.
GOWORK=offdisables workspace mode to mimic external consumers.replaceingo.work(not onlygo.mod) can override versions workspace-wide.
Workspace files are not imported by downstream modules of your libraries.
They coordinate developers and monorepo CI, not end-user graphs.
go.work vs replace in go.mod
| Mechanism | Scope | Typical commit policy |
|---|---|---|
go.work + use | All listed modules in dev | Commit in monorepos for dev ergonomics |
replace in app go.mod | Building app as main | Avoid machine paths on shared branches |
replace in library go.mod | Consumers of library | Only for permanent forks |
Go Notes
# Add a module later
go work use ./newservice
# Sync workspace go line with modules
go work sync
# Run tests across workspace
go work edit -json
go test ./...// go.work with replace (workspace-level)
go 1.26
use (
./app
./lib
)
replace example.com/legacy => ./legacyGotchas
- Forgetting
GOWORK=offin release pipelines may hide the fact that unpublished local code is required. - Committing go.work without documenting intent confuses contributors who expect pure module mode.
- Workspace does not publish transitive local modules - tag and push
libbefore external users can resolveapp. - Multiple go.work files - only the one found by walking parents applies; keep a single root file when possible.
- IDEs must enable Go workspace support - most do when
go.workis present.
Alternatives
- Single module monorepo - Simplest graph; use when one semver line is enough.
- replace in go.mod - Quick one-off; poor for shared teams without standard paths.
- Published pseudo-versions - CI publishes dev versions from feature branches.
FAQs
Should I commit go.work?
Yes in team monorepos where everyone develops multiple modules together.
Solo library authors consuming their module via proxy usually omit it.
Does go.work change what go mod tidy writes?
Tidy still edits each module's go.mod.
Workspace affects resolution during build, not the publishing contract inside each module.
How do I disable workspace mode temporarily?
GOWORK=off go test ./... or rename/move go.work for a single command session.
Can go.work reference modules outside the repo?
use paths are filesystem directories.
Remote modules are still fetched unless you add replace in go.work.
What is go work sync?
Aligns go toolchain lines across workspace modules and can update use directives after module changes.
Do consumers download my local lib folder?
No - they resolve versions from the module proxy per app go.mod.
You must tag and publish lib releases.
Can I have nested modules?
Each module needs its own go.mod root in use.
Workspace does not flatten nested graphs automatically.
Does workspace affect vendoring?
Vendor runs per main module; workspace still resolves locals first.
Verify -mod=vendor builds in CI without go.work when testing publishability.
How many modules belong in one workspace?
As many as you actively co-develop.
Split workspaces when unrelated products share a Git repo but not release cadence.
Is go.work the same as npm workspaces?
Conceptually similar for local linking, but Go keeps separate go.mod files and MVS per module.
Related
- Pseudo-Versions, replace & retract Directives - replace alternative
- go.mod, go.sum & Minimal Version Selection - Per-module graphs
- Standard Project Layout for Services and Libraries - Monorepo layout
- Packages & Modules Basics - Workspace quick start
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).