The import mess that never ends
You write a function that needs fmt, os, and net/http. You add the imports. Later you refactor and remove the os usage. You forget to delete the import line. The compiler rejects the file. You delete it. Two weeks later you add a third-party package. You paste the import at the bottom of the block. Your teammate puts it at the top. The merge conflict arrives. Go has a tool that runs in the background, fixes the mess, and enforces a single source of truth for import ordering. It is called goimports.
What goimports actually does
goimports is a drop-in replacement for gofmt. It applies every formatting rule the standard tool uses, but it also manages your import block. It adds missing imports, deletes unused ones, sorts them into the standard three-tier grouping, and fetches remote modules when you type a URL-style import path. Think of it as a librarian who not only straightens the books on the shelf but also checks out new ones and returns the ones nobody reads.
The tool does not change your logic. It only touches whitespace, indentation, and the import declaration block. The rest of your code stays exactly as you wrote it, except for the formatting rules that gofmt already enforces. This separation keeps the tool predictable. You can run it on every save without worrying about accidental refactors.
gofmt is mandatory in the Go community. Do not argue about indentation or brace placement. Let the tool decide. Most editors run it on save. goimports extends that culture by handling the one thing gofmt deliberately ignores: import management.
The minimal setup
Install the tool via the module proxy if you have not already. The command pulls the latest release and places the binary in your Go bin directory.
# Fetches the latest release from the official x/tools repository
go install golang.org/x/tools/cmd/goimports@latest
Once the binary is in place, you can format a single file or an entire directory tree. The -w flag writes the changes directly to disk. Omitting it prints the formatted output to standard output so you can preview the diff before committing.
# Rewrites the file in place after fixing imports and formatting
goimports -w main.go
# Walks the directory tree and updates every Go source file
goimports -w ./...
The tool reads your go.mod file to understand module boundaries. It uses that information to decide which imports belong in the standard library group, which belong to third-party packages, and which belong to your own project. You do not need to configure it for basic usage.
Run the formatter once. Watch the import block snap into order. Trust the tool to keep your files consistent.
How it resolves and rewrites
The tool works in three distinct phases. It parses your source file into an abstract syntax tree. It scans the tree to find every identifier that references an external package. It compares those references against the current import block. If a package is used but missing, the tool queries the module proxy or your local cache to find the correct import path. If a package is imported but never used, the tool marks it for deletion.
The grouping logic follows a strict convention. Standard library imports come first. Third-party imports follow. Local project imports come last. Within each group, paths are sorted alphabetically. The tool inserts a blank line between groups to make the boundaries obvious. This convention exists because human eyes scan vertically. Grouping reduces cognitive load when you skim a file.
// Standard library first, sorted alphabetically
import (
"context"
"fmt"
"net/http"
)
// Third-party packages follow, separated by a blank line
import (
"github.com/lib/pq"
"golang.org/x/sync/errgroup"
)
// Local project imports come last
import (
"github.com/myorg/myproject/internal/db"
"github.com/myorg/myproject/pkg/auth"
)
The tool respects your go.mod replace directives. If you override a third-party module with a local path, goimports uses the local path in the import block. It also respects vendor directories when present. The resolution step happens before the rewrite step, so the tool never guesses. It either finds the package or it leaves the import alone and prints a warning.
The rewrite phase generates a new import block and patches the file. It preserves your original comments attached to imports. It keeps line lengths reasonable. It runs the standard formatter on the rest of the file to guarantee that indentation and spacing match the community baseline.
Let the tool handle the plumbing. Focus on the business logic.
Real-world workflow and conventions
Integrating goimports into your editor removes the manual step entirely. Most Go editors support it out of the box. In VS Code, you override the default formatter setting so the extension calls goimports instead of gofmt when you save.
{
// Tells the Go extension to run goimports on every save
"go.formatTool": "goimports",
// Automatically organizes imports without prompting
"go.useLanguageServer": true
}
The editor runs the tool in the background. You type a package name. The tool fetches the module. The import appears at the top of the file. You delete a function that used the package. The import vanishes on the next save. The workflow feels automatic because the tool runs fast and rarely conflicts with your typing.
Continuous integration pipelines use the tool to enforce consistency across contributors. The -d flag prints a unified diff instead of writing to disk. It returns a non-zero exit code when changes are required. This behavior makes it ideal for pre-commit hooks or CI checks.
# Prints a diff of required changes without modifying files
goimports -d ./...
The command fails the build if any file needs formatting. Teams use this to prevent style drift. The diff output shows exactly which imports were added, removed, or reordered. Reviewers can see the mechanical changes without scanning the entire file.
You can also configure the tool to prioritize your organization's packages. The -local flag tells the tool to treat a specific domain as the local group. This keeps your internal packages together and separates them from third-party dependencies.
# Groups all github.com/myorg imports into the local tier
goimports -local=github.com/myorg -w ./...
The flag does not change the sorting algorithm. It only changes which group a path belongs to. Standard library imports still come first. Third-party imports still follow. Your organization's packages move to the bottom group. The convention makes large codebases easier to navigate.
Run the diff check in CI. Fix the warnings before they become merge conflicts.
When the tool fights back
The tool relies on network access to resolve unknown imports. If your machine cannot reach the module proxy, the resolution step fails. The tool prints goimports: cannot find package and leaves the import block unchanged. You can work around this by ensuring your module cache is populated or by running go mod download before formatting.
Local development servers sometimes block outbound requests. Corporate proxies require authentication. The tool does not prompt for credentials. It fails silently or returns a network error. You can bypass the proxy by setting the GOPROXY environment variable to direct or by configuring your system proxy settings.
# Forces the tool to skip the proxy and read from the local cache
GOPROXY=direct goimports -w ./...
The -local flag can cause unexpected grouping if you use a wildcard or a path that does not match your module name. The tool matches import paths against the string you provide. If your module is github.com/myorg/myproject but you pass -local=github.com/myorg, the tool groups github.com/myorg/other-repo into the local tier. This is usually fine, but it can blur the line between internal and external dependencies. Match the flag to your actual module path to keep the groups accurate.
The tool sometimes conflicts with manual import comments. If you attach a build tag or a custom comment to an import, the tool preserves it but may reposition it. The formatter does not understand custom metadata. It only understands standard Go syntax. If your project relies on non-standard import annotations, run the tool with -l to list files that need changes, review them manually, and apply the fixes selectively.
The compiler rejects unused imports with imported and not used: package. The tool prevents this error by design. If you see it after running goimports, the tool failed to parse the file or the import is used inside a string literal or a build-tagged file that the tool did not scan. Check your build constraints and run the tool on the specific file.
Do not fight the formatter. Fix the network, adjust the flag, or run the tool on the correct subset of files.
Choosing your formatter
Use gofmt when you only need whitespace and indentation fixes without touching import blocks. Use goimports when you want automatic import resolution, unused import removal, and standard three-tier grouping. Use manual formatting when you maintain a legacy codebase that relies on non-standard import comments or custom build tags that break automated parsing. Use the -d flag in CI pipelines to enforce consistency without modifying files. Use the -local flag when your project spans multiple repositories under a single organization domain.
The tool does not replace your judgment. It replaces the tedious part of import management. Run it on save. Trust the grouping rules. Let the merge conflicts disappear.