Go Workspaces with go.work

Develop several Go modules together with go work init and go work use, without adding replace lines to go.mod, and know when to commit go.work.

When you change a library and an app that uses it at the same time, you want the app to build against your local copy of the library. Before Go 1.18 the only way was a replace line in go.mod, which was easy to commit by accident. Workspaces solve this with a separate go.work file.

The Problem

Two modules side by side:

projects/
  mathx/   go.mod: module example.com/mathx
  app/     go.mod: module example.com/app, imports example.com/mathx

Building app fails because example.com/mathx is not published anywhere:

main.go:5:2: no required module provides package example.com/mathx; to add it:
	go get example.com/mathx

Creating a Workspace

From the parent folder:

cd projects
go work init ./app ./mathx

This creates projects/go.work:

go 1.24.4

use (
	./app
	./mathx
)

Now cd app && go run . builds against the local mathx folder. Edits to mathx are picked up immediately, and neither go.mod file was changed.

Adding and Removing Modules

go work use ./another-lib     # add a module
go work use -r .              # add every module found under this folder
go work edit -dropuse ./mathx # remove one

go env GOWORK prints which go.work file is active. Setting GOWORK=off ignores it for one command, which is a quick way to check that a module builds on its own:

GOWORK=off go build ./...

Building a folder that is not in the workspace:

When a go.work file exists above you, running a command in a module that it does not list fails with directory cmd/shop is contained in a module that is not one of the workspace modules listed in go.work. Add it with go work use, or run with GOWORK=off.

Should go.work Be Committed?

Usually not. A workspace describes how you have folders laid out on your machine. The Go team recommends against committing it in most cases, so add go.work and go.work.sum to .gitignore.

The exception is a monorepo where all modules always live together in one repository. Committing go.work there gives everyone the same setup.

CI without the workspace:

If CI builds each module with GOWORK=off but developers always use a workspace, code that only works because of the local mathx changes passes locally and fails in CI. Before pushing, publish the library change, then go get the new version in the app.

Workspaces used well:

Local multi-module work happens through go.work, go.mod files contain no local path replaces, and each module still builds with GOWORK=off.

Summary

A go.work file, created with go work init and extended with go work use, makes several local modules build against each other without touching their go.mod files. Use GOWORK=off to check modules on their own, keep go.work out of version control unless the repository is a monorepo, and publish library changes before relying on them in CI.