Packages & Modules Basics
10 examples to get you started with Packages & Modules - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a workspace directory:
mkdir moddemo && cd moddemo. - Initialize a module:
go mod init example.com/moddemo. - Run snippets from the module root with
go run .after saving them asmain.go(or the path shown).
Basic Examples
1. Initialize a module
go mod init writes go.mod with your module path.
// Run in shell first:
// go mod init example.com/moddemo- The module path should match where code will be hosted (or use
example.com/...for learning). go.modis the versioned root for every package under this directory tree.- Subdirectories add import path segments; they do not need their own
go.modunless you split modules.
Related: Packages and Modules: Go's Unit of Distribution - packages vs modules mental model
2. Create and import a local package
Split code across packages under the same module.
// greet/greet.go
package greet
import "fmt"
func Hello(name string) string {
return fmt.Sprintf("hello, %s", name)
}// main.go
package main
import (
"fmt"
"example.com/moddemo/greet"
)
func main() {
fmt.Println(greet.Hello("Go"))
}- Import path is
module path + /directory. - Only exported names (capitalized) are visible outside the package.
- Run from module root:
go run .
Related: Package Naming & internal/ Directories - naming and visibility rules
3. Add a dependency
go get records a semver requirement in go.mod.
go get github.com/google/uuid@latestpackage main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
fmt.Println(uuid.NewString())
}go getupdatesgo.modandgo.sum.- Builds download modules into the module cache automatically.
- Pin a specific version with
@v1.6.0instead of@latest.
Related: go.mod, go.sum & Minimal Version Selection - how versions resolve
4. Tidy the module graph
Remove unused requirements and add missing ones.
go mod tidy- Run after refactors that add or drop imports.
- CI should fail if
go mod tidywould change files - usego mod tidy -diffor commit hooks. - Keeps
go.sumaligned with actual imports.
Related: Packages, Modules & Project Layout Best Practices - hygiene habits
5. List module versions
Inspect what the toolchain selected.
go list -m all
go list -m -versions github.com/google/uuidallprints the build list for the current module.-versionsqueries available tags from the origin (network required).- Useful when debugging unexpected upgrades.
Related: Pseudo-Versions, replace & retract Directives - overrides and bad releases
6. Use internal/ for private packages
internal/ blocks imports from outside the parent tree.
moddemo/
go.mod
cmd/app/main.go
internal/config/config.go
// internal/config/config.go
package config
const Port = 8080// cmd/app/main.go
package main
import (
"fmt"
"example.com/moddemo/internal/config"
)
func main() {
fmt.Println(config.Port)
}- Code outside
example.com/moddemocannot importinternal/config. - Prefer
internal/over comments for "do not use" APIs. - Keeps public surface area small for libraries.
Related: Standard Project Layout for Services and Libraries - cmd and internal layout
7. Blank import for registration side effects
Import for init only, not direct symbol use.
package main
import (
"fmt"
_ "example.com/moddemo/register" // triggers init in register package
)
func main() {
fmt.Println("drivers registered")
}// register/register.go
package register
func init() {
// register plugin, driver, or http handler
}- Blank import runs
initfunctions in dependency order. - Common for database drivers (
_ "github.com/lib/pq") and image formats. - Do not use when you need to call exported functions from the package.
Related: Import Cycles, Blank Imports & Dot Imports - when side-effect imports fit
Intermediate Examples
8. Local replace for a fork or sibling module
Point a requirement at a local path during development.
// go.mod snippet
module example.com/moddemo
go 1.26
require example.com/lib v0.0.0
replace example.com/lib => ../libreplaceis for development; avoid committing machine-specific paths to shared main branches without documentation.- Consumers of your published module do not see unpublished
replacepaths on your laptop unless you commit them. - Use
go.workfor multi-module repos when several modules edit each other daily.
Related: go work: Workspace Mode for Multi-Module Repos - workspace without replace hacks
9. Workspace mode with go.work
Develop multiple modules together without editing each go.mod.
# From repo root with sibling modules app/ and lib/
go work init ./app ./lib
go work use ./app ./lib// app/go.mod
module example.com/app
go 1.26
require example.com/lib v0.0.0go.workapplies only on your machine; do not publish it as the library contract.GOWORK=offforces module-only mode for reproducible CI comparisons.- Add
uselines as new modules join the monorepo.
Related: go work: Workspace Mode for Multi-Module Repos - full workspace workflow
10. Standard service layout sketch
Thin main, logic in packages, private code in internal/.
myapp/
go.mod
cmd/myapp/main.go
internal/server/server.go
pkg/client/client.go // optional: stable public API for other repos
// cmd/myapp/main.go
package main
import "example.com/myapp/internal/server"
func main() {
server.Run()
}cmd/holds one directory per binary; each ispackage main.internal/is enforced private;pkg/is conventional public API (still your choice to support).- One module can ship multiple commands under
cmd/.
Related: Standard Project Layout for Services and Libraries - full layout guidance
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).