What is Go workspace mode, and what problem does it solve?

5 minadvancedgoworkspacesgo.workmodules

Quick Answer

Workspace mode, introduced in Go 1.18 alongside generics, lets you work on multiple local modules simultaneously without editing each module's go.mod to add temporary replace directives pointing at local paths. A go.work file at the root of your workspace lists the local module directories to include (use ./moduleA and use ./moduleB), and the go command then resolves imports between them using the local, on-disk versions instead of whatever's published, letting you make a change in one module and immediately see its effect in another that depends on it, with no need to commit, tag, or push anything first. go.work is meant to be a local, developer-machine-only file, not checked into version control, since it reflects one developer's specific local setup, not the project's actual dependency requirements.

Detailed Answer

Before workspace mode, working across multiple related modules locally meant either publishing intermediate versions constantly, or hand-editing go.mod replace directives and remembering to revert them before committing.

The old workaround: replace directives

// go.mod, before workspace mode
module github.com/you/app

require github.com/you/shared-lib v1.0.0

replace github.com/you/shared-lib => ../shared-lib   // easy to forget to remove before committing

This worked, but it required editing go.mod itself, and it was a constant source of "oops, I accidentally committed a local replace directive" mistakes.

The workspace mode approach

myproject/
  app/
    go.mod        # module github.com/you/app
  shared-lib/
    go.mod        # module github.com/you/shared-lib
  go.work         # ties them together, lives outside both modules' go.mod files
// go.work
go 1.22

use ./app
use ./shared-lib
cd myproject
go build ./app/...   # resolves github.com/you/shared-lib from ../shared-lib automatically

No replace directive needed in either module's go.mod. Editing shared-lib's code and rebuilding app immediately picks up the change, with neither go.mod file needing any temporary edits at all.

Why go.work shouldn't be committed

go work init ./app ./shared-lib   # creates go.work locally

go.work reflects one specific developer's local directory layout and which modules they happen to be actively co-developing at that moment — it's not part of the project's actual dependency graph, which is why the convention is to keep it out of version control (often via .gitignore), similar to a personal IDE config file.

The problem this solves, concretely

Say you need to make a coordinated change across two or more modules you maintain together, like a shared library and an app consuming it. Workspace mode lets you iterate on both simultaneously, with real, immediate feedback. There's no friction from publishing intermediate versions or managing temporary replace directives by hand.

Related Resources