9.7 Workspace Mode (Go 1.18+)

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.

Workspace Basics

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

Setting Up Workspaces

Creating 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

Manual 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

Removing modules

go work use -r ./module-a

Why removal is useful

Multi-Module Development

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.

Use Cases

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.