Tools: Importing Packages in Go: A Complete Guide (2026)

Tools: Importing Packages in Go: A Complete Guide (2026)

Source: DigitalOcean

By Gopher Guides, Kent Shultz, Haley Mills and Vinayak Baranwal Importing packages in Go is how you reuse code from the standard library, your own module, and third-party modules. This tutorial walks through the import keyword, module-aware imports, package aliases, blank imports, dot imports, dependency management with go.mod and go.sum, and workspace mode with go.work. You build small runnable examples in each step: generating random numbers, importing a third-party UUID package, importing a local package inside the same module, resolving package name collisions, and automating import formatting with goimports. By the end, you have a practical model for how package paths, module paths, and import syntax fit together in real Go projects. Modules are versioned collections of packages. If you need a deeper module primer, read How To Use Go Modules. Install Go for your operating system: A package in Go is a directory of .go files that share the same package declaration. You import packages by import path, then reference exported identifiers from the imported package name. The package name and import path are related, but they are not the same thing: When you write import "net/http", the compiler looks inside $GOROOT/src, where the Go standard library lives. When you write import "github.com/google/uuid", it looks in the module cache at $GOMODCACHE. You never manage these paths manually. Understanding the lookup order explains two common situations: why your IDE resolves standard library packages instantly without any setup, and why a third-party package causes a cannot find module providing package error until you run go get. If a replace directive is present in go.mod, or if a go.work file is active, those paths take precedence over the module cache. This is how workspace mode lets you develop a local version of a dependency without publishing it first. Packages live inside modules. A single module often contains many packages, including package main entrypoints and reusable library packages. In this layout, main.go imports myapp/mathutil if module myapp appears in go.mod, or github.com/you/myapp/mathutil if the module path is fully qualified. GOROOT points to the Go toolchain installation, GOPATH is the default workspace area for binaries and module cache, and go.mod defines module-based dependency management for your project. Check each value in your environment: GOPATH still matters for go install destinations and module cache location, but your project does not need to live under $GOPATH/src. Go uses the import keyword to bring package identifiers into the current file. You can import one package per line or use a grouped import block. The compiler rejects unused imports, which keeps import blocks accurate and easy to scan. In practice, you should always use the grouped block form even when importing a single package. The goimports tool enforces this automatically, and the Go style guide discourages chaining multiple single-line import statements. Using blocks from the start also makes it easier to add packages later without restructuring your import declaration. Use standard library imports for common functionality such as output, random numbers, filesystem access, and HTTP clients. This first example uses math/rand. In older Go versions, many examples call rand.Seed(time.Now().UnixNano()). Go 1.20 changes this behavior. In Go 1.20 and later, the global random source is automatically seeded with a random value. Calling rand.Seed() is no longer necessary and the function is deprecated. If you are on Go 1.20+, remove explicit seeding code. If you want explicit, local random sources, use rand.New: This pattern keeps randomness scoped to one *rand.Rand instance and avoids package-global state. Third-party imports use fully qualified module paths, and go.mod plus go.sum track versions and checksums. Inspect go.mod again: require directives pin module versions. The // indirect suffix means the module is not imported directly by source code in the current module at that moment, but it is still required in the build graph. go.sum stores cryptographic hashes for downloaded modules and their go.mod files. Commit both go.mod and go.sum to version control. The go.sum file records cryptographic hashes of each module version and its go.mod file, which lets Go verify future downloads. When dependencies drift, clean the module graph: Run go mod tidy after adding or removing imports, before committing, and in CI checks. Local package imports inside a module use the module path prefix from go.mod, not relative paths like ./mathutil. First, create the mathutil directory: Then create mathutil/mathutil.go: If you see cannot find module providing package github.com/sammy/random/mathutil, confirm two things: the mathutil/ directory exists at the root of your module, and the module path in go.mod matches the prefix used in your import statement exactly. Use aliases when two imports expose the same package name or when a long import path reduces readability. When two packages share the same base name, assign an alias to one of them in the import block. The alias replaces the package name everywhere in that file: You can also alias for readability when a package name is ambiguous in context: This optional section keeps the larger side-by-side example and adds Go 1.20 guidance for crypto/rand. As of Go 1.20, crand.Read() is deprecated. Use io.ReadFull(crand.Reader, b) as shown here. A blank import runs a package’s init() function without exposing its exported identifiers to your code. Common use cases include SQL drivers, image decoders, and plugin registration packages. Without the blank import, the driver’s init() does not register "postgres" with database/sql, and sql.Open("postgres", ...) returns an unknown driver error. Blank imports are intentional. If your linter reports an unused import, first confirm that the package registers behavior through init() before suppressing the warning. Read more about initialization behavior in Understanding init in Go. Dot imports copy exported identifiers from another package into the current file namespace. Avoid dot imports in production code. They hide identifier origins and can create conflicts when different packages export the same name. The one widely accepted use case is in test files, where dot-importing the package under test removes the need to qualify every identifier. For example: Without the dot import, every reference to Double would need to be written as mathutil.Double. In assertion-heavy test files, dot imports reduce that repetition. Outside of test files, the tradeoff does not hold: the readability cost of hidden identifier origins outweighs the saved keystrokes. The go mod commands manage the full lifecycle of your module’s dependencies: creating the module, adding and removing packages, and keeping go.mod and go.sum consistent with your source code. Every project that uses external packages needs a go.mod file. Create one with: Use go get with a version suffix to add a new package or upgrade an existing one: To pin to the latest released version, use @latest instead of a specific version tag. Pass @none as the version to remove a package from the module graph: After adding, removing, or reorganizing imports in your source files, run: go mod tidy does two things: it adds any missing require entries for packages your code imports, and it removes require entries for packages your code no longer uses. If you remove an import from your source but forget to run go mod tidy, the stale entry stays in go.mod and bloats your dependency graph. Run it before every commit and in CI. If go mod tidy removes a package you intended to keep, check whether that package is still imported anywhere in your source. If it is not, the removal is correct. If it is, verify that your import path matches the module path exactly, including capitalisation. To download all dependencies to your local cache without building: This is useful in CI pipelines where you want to separate the download step from the build step. To confirm that your locally cached modules have not been modified since download: Workspace mode solves a specific problem: you are developing a library (mylib) and an application (myapp) that depends on it, and you want to test changes to mylib in myapp without publishing a new version of mylib first. Before workspace mode, the only way to do this was a replace directive in go.mod, which you had to remember to remove before committing. Workspace mode handles this with a separate go.work file that stays outside version control. Set up a workspace with two modules: Verify the workspace file was created correctly: To add a module to an existing workspace after initialization, run go work use ./module-path. go.work files are typically local-development artifacts. Add go.work and go.work.sum to .gitignore unless your repository is intentionally organized as a shared multi-module workspace. goimports formats Go source files and fixes imports in one pass. It removes unused imports and adds missing imports when it can resolve package paths. Run a diff-only rewrite: Write changes back to file: For editor-driven import management, gopls provides integrated auto-import behavior in supported IDEs. For CI pipelines and pre-commit hooks, goimports remains a reliable command-line choice. These are the errors Go developers encounter most often when working with imports, and what each one means. imported and not used Go will not compile a file that imports a package without using at least one of its exported identifiers. Remove the unused import, or replace it with a blank import (_) if you need it for side effects only. cannot find module providing package This error means the package path in your import statement does not match any module in your build graph. The most common causes are: Run go get <package-path> to add the missing module, then verify your import path matches pkg.go.dev exactly. package X is not in GOROOT This error appears when you use a bare package name that the compiler expects to find in the standard library but cannot. It usually means you typed a partial import path (for example, "rand" instead of "math/rand"). Check the full import path on Go Standard Library. This happens when two modules in your dependency graph provide the same package path. It is uncommon but occurs with forked or renamed modules. Use a replace directive in go.mod to pin the version you want, or alias one of the imports to disambiguate at the call site. // indirect stays after adding an import After you add an import to your source code, the // indirect comment on the corresponding require line in go.mod disappears only after you run go mod tidy. Until then, Go has not re-evaluated which packages are directly imported. Run go mod tidy to update the annotation. Q: How do you import a package in Go? A: Use the import keyword and specify the package path as a string. For example, import "fmt" imports the fmt package. You then reference exported members with fmt.Println. Q: Which keyword is used to import packages in Go? A: Go uses the import keyword. It accepts single-line imports or grouped imports in parentheses. Unused imports trigger a compiler error. Q: How do you import multiple packages in Go? A: Use an import block with parentheses. List each path on its own line: Tools like goimports keep this block sorted and consistent. Q: How do packages work in Go? A: A package is a directory of source files sharing the same package clause. Other files import that package by import path, and only exported identifiers are accessible. Packages are versioned and distributed through modules. Q: What is the difference between GOPATH and Go modules? A: GOPATH is a workspace location that still hosts installed binaries and module cache. Go modules are the dependency system used by current Go development, based on go.mod and go.sum. Your project no longer depends on being inside GOPATH/src. Q: What does a blank identifier import do in Go? A: A blank import (_ "pkg/path") imports a package only for side effects. It runs init() and discards direct identifier access. This pattern is common with SQL drivers and codec registration packages. Q: When should you use a package alias in Go? A: Use aliases when two packages have the same name, or when a path is long and clarity improves with a short local name. Good aliases stay descriptive, such as crand for crypto/rand. Avoid aliases that obscure package purpose. Q: What is the difference between go get and go install? A: go get adds or updates a package in your current module’s go.mod and makes it available to import in your code. go install compiles and installs an executable binary to $GOPATH/bin. Use go get when you want to use a package as a library dependency. Use go install when you want to install a command-line tool, such as goimports or staticcheck. Q: How do I know whether a package is in the standard library or third-party? A: Standard library packages have short, unqualified import paths with no domain prefix, such as fmt, os, net/http, or crypto/rand. Third-party packages always include a domain, such as github.com/google/uuid. Browse the full standard library at pkg.go.dev/std. Use go doc <package> in your terminal to read documentation for any package already in your module graph. You now have a practical import workflow for standard library packages, module dependencies, local packages, aliases, blank imports, dot imports, and workspace-mode development. These patterns cover day-to-day package management in modern Go projects. Continue with How To Write Packages in Go, then revisit How To Use Go Modules for deeper dependency workflows. For related topics, read Understanding init in Go, How To Use the Flag Package in Go, and How To Build Go Executables for Multiple Platforms on Ubuntu. Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases. Learn more about our products Go (or GoLang) is a modern programming language originally developed by Google that uses high-level syntax similar to scripting languages. It is popular for its minimal syntax and innovative handling of concurrency, as well as for the tools it provides for building native binaries on foreign platforms. Browse Series: 53 tutorials This textbox defaults to using Markdown to format your answer. You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link! Please complete your information! Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation. Full documentation for every DigitalOcean product. The Wave has everything you need to know about building a business, from raising funding to marketing your product. Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter. New accounts only. By submitting your email you agree to our Privacy Policy Scale up as you grow — whether you're running one virtual machine or ten thousand. Sign up and get $200 in credit for your first 60 days with DigitalOcean.* *This promotional offer applies to new accounts only.