Workspace mode, introduced in Go 1.18, allows you to work with multiple modules simultaneously without publishing them or modifying their go.mod files. This is particularly useful for multi-module projects and local development.
What is workspace mode?
Workspace mode lets you work with multiple Go modules in a single workspace. You can make local changes to modules and have them immediately available to other modules in the workspace, without needing to publish or use replace directives.
Workspace mode exists because before workspaces, working with multiple local modules required replace directives (modifying go.mod files), publishing modules (pushing to version control), and manual updates (updating go.mod for each change).
A workspace is defined by a go.work file that lists the modules in the workspace. Go tools use this file to resolve dependencies, preferring local workspace modules over published ones.
Basic workspace structure
workspace/
├── go.work
├── module-a/
│ ├── go.mod
│ └── main.go
└── module-b/
├── go.mod
└── lib.go
Why this structure helps
replace directives neededCreating a workspace
go work init ./module-a ./module-b
This creates a go.work file:
go 1.23
use (
./module-a
./module-b
)
Why go work init
go.work with correct pathsManual workspace file
You can also create go.work manually:
go 1.23
use (
./module-a
./module-b
../external-module
)
Why manual creation
Adding modules to workspace
go work use ./new-module
Why go work use
go.work correctlyRemoving modules
go work use -r ./module-a
Why removal is useful
Local module development
// module-a/go.mod
module example.com/module-a
go 1.23
require example.com/module-b v0.0.0-00010101000000-000000000000
// module-b/go.mod
module example.com/module-b
go 1.23
// go.work
go 1.23
use (
./module-a
./module-b
)
Why this works
When module-a imports module-b, Go resolves it to the local module-b in the workspace instead of trying to download it.
Testing local changes
# Make changes to module-b
cd module-b
# Edit code
# Test in module-a (sees changes immediately)
cd ../module-a
go test
Immediate testing is possible because changes are available immediately (no publishing needed), enables fast iteration (test changes without commit/push), and provides isolated testing (test in workspace only).
Multiple versions
// go.work
go 1.23
use (
./module-a
./module-b-v1
./module-b-v2
)
Multiple versions enable migration testing (test migration between versions), comparison (compare different implementations), and development (work on multiple branches).
Dependency resolution
Workspace modules take precedence over published modules:
Published: example.com/module-b v1.2.3
Workspace: ./module-b (local)
Result: Uses ./module-b (workspace module)
Precedence matters because workspace modules always use the local version (local development), workspace overrides published modules (no conflicts), and allows testing local changes without affecting others.
1. Monorepo development
monorepo/
├── go.work
├── services/
│ ├── api/
│ │ └── go.mod
│ └── worker/
│ └── go.mod
└── shared/
├── lib-a/
│ └── go.mod
└── lib-b/
└── go.mod
// go.work
go 1.23
use (
./services/api
./services/worker
./shared/lib-a
./shared/lib-b
)
Workspaces are useful for monorepos because they manage shared libraries (multiple services use shared code), enable local changes (test changes across services), and allow working with unpublished shared code.
2. Library development
project/
├── go.work
├── library/
│ ├── go.mod
│ └── lib.go
└── example/
├── go.mod
└── main.go
Why this helps
3. Plugin development
plugin-system/
├── go.work
├── core/
│ └── go.mod
└── plugins/
├── plugin-a/
│ └── go.mod
└── plugin-b/
└── go.mod
Why workspaces for plugins
4. Microservices development
services/
├── go.work
├── gateway/
│ └── go.mod
├── user-service/
│ └── go.mod
└── order-service/
└── go.mod
Workspaces are useful for microservices because they manage shared code (common libraries across services), enable local testing (test service interactions), and support development (develop services together).
Best practices
1. Version control
# Include go.work in version control
git add go.work
# But consider .gitignore for personal workspaces
echo "go.work" >> .gitignore # For personal use
Version control helps with team consistency (same workspace for team), reproducibility (same module setup), and documentation (shows project structure).
2. Keep workspace focused
// ✅ GOOD: Related modules only
go 1.23
use (
./module-a
./module-b
)
// ❌ BAD: Too many unrelated modules
go 1.23
use (
./module-a
./module-b
./module-c
./module-d
// ... 20 more modules
)
3. Document workspace purpose
// go.work
// This workspace includes the main application and its dependencies
// for local development. Modules here override published versions.
go 1.23
use (
./app
./lib
)
4. Use for development, not production
Workspaces are for development. Production builds should use published modules or proper replace directives.
5. Clean up unused modules
# Remove modules no longer needed
go work use -r ./old-module
Limitations
1. Not for production
Workspaces are development tools. Don't commit go.work to production builds.
2. Path dependencies
Workspace modules must be accessible via file paths. Can't use workspaces for remote-only development.
3. Version conflicts
If workspace and published modules have incompatible versions, workspace takes precedence (may cause issues).
4. Build tool support
Some build tools may not understand workspaces. Standard Go tools work fine.
Commands
go work init [dir...] # Initialize workspace
go work use [dir] # Add module to workspace
go work use -r [dir] # Remove module from workspace
go work edit # Edit go.work file
go work sync # Sync workspace with modules
Workspace mode is a powerful tool for multi-module development. It simplifies local development by allowing you to work with multiple modules without publishing or using replace directives. Use it for monorepos, library development, and any scenario where you need to work with multiple related modules.