Develop across multiple Go modules simultaneously without publishing. A visual companion to the official Go tutorial.
When you develop a library and its consumer at the same time, Go
normally fetches the library from the module cache — the published
version. To use local changes, you'd add
replace directives in go.mod. That's fragile
and easy to accidentally commit. Workspaces solve this cleanly.
Manual replace directives in go.mod.
Easy to commit by mistake. One module at a time.
A go.work file at the root. Never committed. Multiple
modules resolve locally and automatically.
Start by setting up a directory and initializing a new Go module that imports an external package.
$ mkdir workspace && cd workspace $ mkdir hello && cd hello $ go mod init example.com/hello go: creating new go.mod: module example.com/hello $ go get golang.org/x/example/hello/reverse
Now create the entry point that uses the reverse package:
package main import ( "fmt" "golang.org/x/example/hello/reverse" ) func main() { fmt.Println(reverse.String("Hello")) }
$ go run . olleH
reverse from the remote module
cache
Go back to the workspace root and create a go.work file.
This tells Go which modules belong to your local workspace.
$ cd workspace $ go work init ./hello
This generates a go.work file:
go 1.18 use ./hello
go.work syntax mirrors go.mod. The
use directive lists local module directories. The
go directive sets the minimum Go version.
With the workspace active, you can build and run any module from the
root directory — Go resolves all use-listed modules
automatically.
$ go run ./hello olleH
go run ./hello from
outside the workspace (where no
go.work exists) would fail — Go wouldn't know which
modules to use.
Clone the dependency's repository and add it to the workspace. Now Go resolves imports from your local copy instead of the module cache.
$ git clone https://go.googlesource.com/example Cloning into 'example'... $ go work use ./example/hello
The go.work file now includes both modules:
go 1.18 use ( ./hello ./example/hello )
reverse from your local clone,
not the module cache
Add a new function to the cloned reverse package and
immediately use it from your hello module — no publishing
required.
Create a new file in the cloned dependency:
package reverse import "strconv" // Int returns the decimal reversal of the integer i. func Int(i int) int { i, _ = strconv.Atoi(String(strconv.Itoa(i))) return i }
Update your module to call the new function:
package main import ( "fmt" "golang.org/x/example/hello/reverse" ) func main() { fmt.Println(reverse.String("Hello"), reverse.Int(24601)) }
$ go run ./hello olleH 10642
go.work file acts as a local override layer. Since
both modules are in the same workspace, changes in one are
immediately visible to the other during builds.
When you're done developing, release the modified dependency, pin the
version in your go.mod, and retire the workspace.
go.mod,
and the workspace is no longer needed
go.work to version control. It's a local
development convenience — your teammates might have different
directory layouts. The go.mod file is what gets
committed and shared.
go.work file with one or more initial
modules
-r to scan subdirectories recursively
go.work file programmatically, similar to
go mod edit
go.mod