Go projects often start as a single module. As an application grows, however, its code may be divided into several modules to make development, testing, and maintenance easier.

For example, a company might maintain a backend API, a shared logging library, and an authentication library in separate Go modules. Each module has its own go.mod file and can be versioned independently.

The challenge appears when you need to develop these modules together.

Suppose your backend API depends on a shared library. You make a change to the library and want to test it immediately in the API. Without an appropriate development setup, you may need to publish a new library version, update the API's dependency, or manually change its module configuration.

These steps can slow down development and make it harder to test changes across related projects.

Go workspaces provide a solution. A workspace allows you to work with multiple local modules together without permanently changing their dependency versions or publishing every change before testing it.

In this article, you will learn how Go workspaces work, how to create a workspace, how to develop multiple modules together, how to run tests across modules, and how to avoid common dependency-management problems.

What Is a Go Workspace?

A Go workspace is a development environment that allows multiple Go modules to be used together.

Go introduced workspace support in Go 1.18. A workspace is commonly defined by a go.work file, which identifies the local modules that should participate in workspace mode.

Consider the following project structure:

Code
go-projects/
├── go.work
├── user-service/
│   ├── go.mod
│   └── main.go
├── auth-library/
│   ├── go.mod
│   └── auth.go
└── logger-library/
    ├── go.mod
    └── logger.go

Each directory is an independent Go module.

The user-service module can depend on auth-library and logger-library. During development, the workspace allows Go to resolve those dependencies from their local directories.

This means you can modify the authentication library, run the user service, and test the combined changes without publishing a new authentication library version.

A workspace does not merge the modules into one module. Each module keeps its own go.mod, dependencies, and module path.

The workspace simply tells Go which local modules should be considered together during development.

Go Workspace vs. Go Module

Before creating a workspace, it is useful to understand the difference between a module and a workspace.

A Go module defines a collection of related Go packages with its own dependency configuration. A workspace brings multiple local modules into a shared development environment.

Feature

Go module

Go workspace

Main configuration

go.mod

go.work

Purpose

Defines a module and its dependencies

Uses multiple local modules together

Number of modules

Represents one module

Can include multiple modules

Dependency management

Records module dependencies and versions

Can override dependency resolution for local workspace modules

Typical use

Building and distributing a library or application

Developing related modules together

Publishing

Modules can be versioned and published independently

A workspace itself is not a replacement for module publishing

For example, a shared library might have this module declaration:

Code
module example.com/company/auth-library

go 1.22

The module path identifies the library to other Go modules.

Its own go.mod remains responsible for the library's dependency requirements. A workspace does not eliminate the need to maintain this file.

Create a Workspace with Multiple Modules

Let's build a simple example with two modules:

  • user-service: An application that uses an authentication library.

  • auth-library: A reusable module that provides authentication-related functionality.

Step 1: Create the Project Directories

Create the following directory structure:

Code
go-workspace-demo/
├── user-service/
└── auth-library/

Open a terminal and create the directories:

Code
mkdir go-workspace-demo
cd go-workspace-demo

mkdir user-service
mkdir auth-library

You can create these directories manually if you prefer.

Step 2: Initialize the Authentication Module

Move into the authentication library directory:

Code
cd auth-library
go mod init example.com/company/auth-library

This creates a go.mod file similar to the following:

Code
module example.com/company/auth-library

go 1.22

The Go version in your generated file depends on the installed Go toolchain and the command you use.

Next, create a file named auth.go:

Go
package auth

import "strings"

func IsValidToken(token string) bool {
	return strings.TrimSpace(token) != ""
}

This package provides a simple function that checks whether a token contains non-whitespace characters.

This function is only an example of module integration. It does not implement secure token validation for a real authentication system.

Step 3: Initialize the User Service Module

Return to the project root and enter the service directory:

Code
cd ../user-service
go mod init example.com/company/user-service

This creates a separate go.mod file for the application.

Create main.go:

Go
package main

import (
	"fmt"

	"example.com/company/auth-library"
)

func main() {
	token := "sample-token"

	if auth.IsValidToken(token) {
		fmt.Println("Token is present")
		return
	}

	fmt.Println("Token is missing")
}

The import path matches the authentication library's module path.

However, the library exists only on your local machine and has not been published. To make both modules work together during development, you can configure a workspace.

Create the go.work File

Return to the project root:

Code
cd ..

Initialize a workspace:

Code
go work init ./user-service ./auth-library

Go creates a go.work file that identifies the two local modules.

The generated file will resemble:

Code
go 1.22

use (
	./auth-library
	./user-service
)

The exact Go version may differ depending on your environment.

The use directive tells Go that both directories belong to the workspace.

When workspace mode is active, Go can resolve the example.com/company/auth-library dependency from the local auth-library module.

You do not need to publish the library first or add a permanent local filesystem replacement to the service's go.mod.

How Workspace Dependency Resolution Works

Without a workspace, the service normally resolves dependencies according to its module requirements and the configured module sources.

With the workspace enabled, Go considers the modules listed in go.work. If a dependency corresponds to a module included in the workspace, Go can use that local module during development.

This behavior is especially useful when you are changing both the service and the library.

For example, you can modify auth.go, save the file, and rerun the service. The application uses the updated local code without requiring a new published library version.

Run the Application Using the Workspace

From the project root, run:

Code
go run ./user-service

The application should print:

Code
Token is present

The result confirms that the user service can import and execute code from the local authentication module.

You can also build the service:

Code
go build -o user-service-app ./user-service

The generated executable is created in the current directory.

If the build fails, verify that both modules are listed in go.work, that the import path matches the library's module declaration, and that the required package and function are exported.

In Go, identifiers beginning with an uppercase letter are exported and can be accessed from other packages. That is why the example function is named IsValidToken.

Test Multiple Modules in the Workspace

A workspace becomes particularly useful when you need to test changes across related modules.

You can run tests from the workspace root:

Code
go test ./...

This command tests packages matched by the current directory pattern. It does not automatically mean that every module listed in go.work will be tested as a separate module.

When you need to test every workspace module, use an explicit pattern that matches the module directories:

Code
go test ./user-service/... ./auth-library/...

This command is useful when the workspace contains modules in separate subdirectories.

You can also run tests from an individual module directory:

Code
cd auth-library
go test ./...

This tests the authentication library independently of the service.

Testing each module separately remains important. A workspace can make cross-module development easier, but it should not hide problems that would appear when a module is built and tested on its own.

Add a Unit Test to the Authentication Library

Create auth_test.go inside the auth-library directory:

Go
package auth

import "testing"

func TestIsValidToken(t *testing.T) {
	tests := []struct {
		name  string
		token string
		want  bool
	}{
		{
			name:  "valid token",
			token: "abc123",
			want:  true,
		},
		{
			name:  "empty token",
			token: "",
			want:  false,
		},
		{
			name:  "whitespace token",
			token: "   ",
			want:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsValidToken(tt.token)

			if got != tt.want {
				t.Fatalf(
					"IsValidToken(%q) = %v, want %v",
					tt.token,
					got,
					tt.want,
				)
			}
		})
	}
}

Run the tests from the workspace root:

Code
go test ./user-service/... ./auth-library/...

The test verifies the library's behavior independently while allowing you to maintain the service and library in the same development environment.

For production code, add tests that reflect the actual authentication requirements, including invalid, expired, or incorrectly signed tokens where applicable.

Manage Workspace Modules

As a project grows, you may need to add or remove modules from the workspace.

Go provides commands for managing the go.work file.

Add a Module

Suppose you create another module named logger-library.

From the workspace root, run:

Code
go work use ./logger-library

Go adds the module to the workspace configuration.

You can inspect the result:

Code
cat go.work

The workspace file should now contain the additional module path.

On Windows, you can inspect the file using PowerShell:

PowerShell
Get-Content go.work

Add Multiple Modules

You can add multiple module directories in one command:

Code
go work use ./user-service ./auth-library ./logger-library

This updates the workspace configuration to include the specified modules.

Remove a Module

If you no longer need a module in the workspace, edit go.work and remove its path from the use block.

You can also use:

Code
go work edit -dropuse=./logger-library

The module directory and its go.mod file remain unchanged. The command only removes the module from the workspace configuration.

Inspect Workspace Configuration

Use the following command to print the active workspace file:

Code
go env GOWORK

If Go is using a workspace file, the command normally returns its path.

If workspace mode is disabled, it can return an empty value.

You can also inspect the Go version and other relevant environment settings:

Code
go version
go env GOWORK GOMOD GOPATH

These commands are useful when the application behaves differently across development machines or CI environments.

Understanding GOWORK and Workspace Mode

Go determines whether workspace mode is active based on its environment and workspace configuration.

The GOWORK environment variable can control which workspace file Go uses.

For example, you can explicitly point Go to a workspace file:

Code
GOWORK=/path/to/project/go.work go test ./...

This example uses Unix-style environment variable syntax. In PowerShell, use:

PowerShell
$env:GOWORK = "C:\path\to\project\go.work"
go test ./...

To disable workspace mode for a command, set GOWORK=off:

Code
GOWORK=off go test ./...

On PowerShell:

PowerShell
$env:GOWORK = "off"
go test ./...

Disabling the workspace is useful when you want to verify that a module builds using its own dependency configuration rather than relying on local workspace modules.

Remember that disabling workspace mode does not automatically publish local modules or update their dependency versions. If the service depends on a version that is not available through its normal module sources, the command may fail.

Go Workspaces vs. Replace Directives

Before workspaces were available, developers often used the replace directive in go.mod to point a dependency to a local directory.

For example:

Code
module example.com/company/user-service

go 1.22

require example.com/company/auth-library v0.0.0

replace example.com/company/auth-library => ../auth-library

This tells Go to use the local directory instead of the specified module version.

The approach can work, but it introduces a local filesystem path into the module's dependency configuration.

If the replacement is committed accidentally, other developers and CI systems may fail because the relative directory does not exist in their environments.

A workspace can avoid this problem by keeping local development configuration in go.work, separate from the module's normal dependency requirements.

Consideration

Workspace

Replace directive

Configuration file

go.work

go.mod

Scope

Can include multiple local modules

Overrides a particular module path

Local multi-module development

Designed for this use case

Can support local dependency development

Dependency replacement

Uses local workspace modules

Can replace a module with another path or module

Risk of committing local paths

Lower when go.work is excluded from version control

Possible if a local replacement is committed

Best use

Coordinated development across related modules

Intentional dependency replacement when appropriate

A workspace and a replace directive solve related but different problems. Workspaces are generally a convenient choice when several local modules need to be developed together. A replacement can still be appropriate for specific dependency-management requirements.

Common Go Workspace Problems and Their Solutions

Go Cannot Find a Local Module

If Go reports that it cannot resolve a module, first verify that the module is included in go.work.

Run:

Code
go env GOWORK

Then inspect the workspace file and confirm that the module path in the importing code matches the module declaration in the dependency's go.mod.

For example, if the library declares:

Code
module example.com/company/auth-library

the service should import the corresponding package path:

Go
import "example.com/company/auth-library"

A mismatch between the module path and the import path can prevent the package from being resolved correctly.

The Application Works Locally but Fails in CI

One common reason is that the local machine uses a workspace containing modules that are not available in the CI environment.

For example, the local go.work file might include a module directory that was not checked out in the build pipeline.

Another possibility is that the local build relies on a dependency replacement or uncommitted change.

To identify the issue, test the service with workspace mode disabled:

Code
GOWORK=off go test ./...

If the command fails, inspect the module's dependency requirements and verify that the dependencies are available without the workspace.

A Workspace Change Affects More Modules Than Expected

Workspace mode can change dependency resolution for modules listed in go.work.

This is useful during development, but it can also make a module behave differently from how it would behave when consumed independently.

Test important modules both within the workspace and independently when preparing a release.

Dependency Versions Seem Different

A workspace can affect which local modules Go uses, but it does not mean every dependency automatically receives the newest version.

Inspect the module requirements and the active workspace configuration:

Code
go list -m all
go env GOWORK

The first command displays module dependencies selected in the current module context. Run it from the module directory you want to inspect.

Compare the results with workspace mode enabled and disabled if dependency resolution is unexpected.

The Workspace File Contains Unintended Changes

A workspace file can be generated or edited as modules are added and removed.

Review changes to go.work before committing them. Teams should agree on whether the workspace file belongs in version control, based on their development workflow.

A workspace file that contains machine-specific paths or references to unavailable directories can cause confusion for other developers.

Best Practices for Go Workspaces

Keep Each Module Independently Buildable

A workspace should make development easier without making each module dependent on the workspace for basic correctness.

Maintain a valid go.mod in every module, and test important modules independently before publishing them.

Use Workspaces for Coordinated Development

Workspaces are most useful when modules are closely related and changes often need to be tested together.

They are less necessary when modules rarely change together or when developers consume only released dependency versions.

Keep Local Development Configuration Separate

Use the workspace to coordinate local modules rather than adding temporary filesystem replacements to dependency configuration without a clear reason.

Review go.work changes before sharing them with the team.

Test the Release Configuration

Before releasing a library, test it without relying on workspace-only modules.

This helps identify missing dependencies, incorrect module declarations, and code that works locally but fails for downstream consumers.

Avoid Treating a Workspace as a Dependency Versioning System

A workspace is not a replacement for semantic versioning or dependency management.

When a module is ready for use by other projects, publish or otherwise distribute it according to your team's release process, and update consumers to use the intended version.

Document the Workspace Setup

For repositories containing multiple modules, document how to initialize the workspace, run tests, and build individual modules.

This reduces setup errors for new developers and helps keep local development consistent across the team.

Advantages and Limitations of Go Workspaces

Go workspaces provide several benefits:

  • Developers can modify multiple modules without publishing each intermediate change.

  • Local changes to shared libraries can be tested immediately by dependent applications.

  • Related modules can be developed and debugged together.

  • The go.work file keeps workspace configuration separate from each module's dependency requirements.

  • Teams can test coordinated changes before deciding which module versions to release.

However, workspaces also have limitations:

  • Workspace dependency resolution can differ from the resolution used by a consumer outside the workspace.

  • A local workspace can hide missing dependency versions or incomplete release configuration.

  • Teams need to manage the workspace file carefully when modules live in separate repositories or directories.

  • A workspace does not replace independent module testing, versioning, or publishing.

  • Developers must understand which workspace file is active when debugging dependency issues.

These limitations are manageable when workspaces are treated as development tools rather than as a substitute for normal module dependency management.

Conclusion

Go workspaces provide a practical way to develop and test multiple Go modules together. By defining a go.work file, you can use local versions of related modules without publishing every change or permanently modifying each module's dependency configuration.

The basic workflow is straightforward: initialize each module, create a workspace with go work init, add modules with go work use, and run builds and tests against the combined project.

For production-ready development, continue testing modules independently, verify dependency resolution with workspace mode disabled when appropriate, and maintain a clear release process for publishing module versions.

When used correctly, Go workspaces make coordinated development easier while allowing each module to remain an independently maintained and distributable component.