How to Structure Go Projects for Open Source Libraries: The Complete Guide
Use the standard Go project layout to place public APIs in /pkg, private implementation in /internal, and auxiliary tools in /cmd, creating a directory structure that the Go toolchain recognizes and enforces automatically.
When publishing a Go library, your directory layout signals to users and the compiler which code is intended for public consumption versus internal implementation details. The golang-standards/project-layout repository provides a battle-tested convention that major open-source projects use to organize code for maximum clarity, encapsulation, and maintainability.
Core Directories for Library Organization
/pkg – The Public API
Place reusable packages that other projects should import under /pkg. According to the /pkg/README.md, this directory explicitly signals stable, versioned code ready for external use.
Import paths follow the pattern github.com/youruser/yourlib/pkg/packagename, making the API surface immediately discoverable.
/internal – Encapsulated Implementation
The /internal directory contains packages that cannot be imported by external modules. As documented in /internal/README.md, the Go compiler actively enforces this boundary—attempting to import these packages from another module results in a build error.
Use this for helper code, adapters, and implementation details you want to refactor freely without breaking downstream consumers.
/cmd – Command-Line Tools
Small binaries, demos, or utilities shipped with the library live in /cmd. Each subdirectory typically contains a separate main package. See /cmd/README.md for guidance on organizing multiple CLI tools.
/examples and /test
Standalone programs demonstrating idiomatic usage belong in /examples, while integration tests and large test fixtures reside in /test. These directories, documented in /examples/README.md and /test/README.md, keep auxiliary code separate from the library core.
Why This Layout Works for Open Source Libraries
- Explicit Public API – The
/pkgdirectory creates an obvious import path structure that consumers can rely on for stable imports. - Compiler-Enforced Encapsulation – The
/internaldirectory provides hard boundaries that prevent accidental reliance on implementation details, allowing you to refactor private code without semantic version bumps. - Zero Configuration – The layout works with standard Go tools (
go build,go test,go vet) without additional build scripts or configuration files. - Community Familiarity – Following the golang-standards/project-layout convention reduces onboarding friction for contributors who already recognize where to find code, tests, and documentation.
Setting Up the Module Structure
Place a single go.mod file at the repository root to declare the module path:
module github.com/youruser/yourlib
All packages under /pkg and /internal automatically belong to this module. The Go toolchain resolves imports correctly without extra configuration, and tagging the repository (e.g., v1.2.3) makes versions available through the module proxy immediately.
Practical Implementation Examples
Creating a Public Package in /pkg
// /pkg/hello/hello.go
package hello
// Greet returns a friendly greeting.
func Greet(name string) string {
if name == "" {
name = "world"
}
return "Hello, " + name + "!"
}
Consumers import this using:
import "github.com/youruser/yourlib/pkg/hello"
func main() {
fmt.Println(hello.Greet("Alice"))
}
Hiding Implementation Details in /internal
// /internal/util/strings.go
package util
import "strings"
func toUpper(s string) string {
return strings.ToUpper(s)
}
Attempting to import github.com/youruser/yourlib/internal/util from another module fails at compile time with: use of internal package not allowed.
Building Example Programs
// /examples/hello/main.go
package main
import (
"fmt"
"github.com/youruser/yourlib/pkg/hello"
)
func main() {
fmt.Println(hello.Greet("")) // prints "Hello, world!"
}
Run the example directly:
go run ./examples/hello
Packaging CLI Tools
// /cmd/hello-cli/main.go
package main
import (
"flag"
"fmt"
"github.com/youruser/yourlib/pkg/hello"
)
func main() {
name := flag.String("name", "", "Name to greet")
flag.Parse()
fmt.Println(hello.Greet(*name))
}
Build and install locally:
go build -o bin/hello-cli ./cmd/hello-cli
./bin/hello-cli -name=Bob
Essential Documentation Files
The golang-standards/project-layout repository provides self-documenting README files that explain each directory's purpose:
- /README.md – Overview of the layout and rationale
- /pkg/README.md – Guidelines for public package organization
- /internal/README.md – Rules for encapsulated packages
- /cmd/README.md – Tool and binary organization
- /examples/README.md – Example program structure
- /test/README.md – Integration testing and fixtures
Summary
- Place public, stable APIs in
/pkgto signal importable code and create clear import paths - Use
/internalfor implementation details the compiler protects from external modules - Add
/cmdfor binaries,/examplesfor documentation via working code, and/testfor integration suites - Maintain one
go.modat the repository root to define the module boundary - Reference the golang-standards/project-layout documentation to ensure your structure matches community expectations
Frequently Asked Questions
What is the difference between /pkg and /internal in Go projects?
The /pkg directory contains packages intended for external import and long-term stability, while /internal houses private implementation details. The Go compiler enforces that packages under /internal cannot be imported by code outside the module that contains them, providing automatic API boundaries that prevent accidental dependency on unstable internals.
Should I put all my library code in /pkg or just the public API?
Only place packages you explicitly intend for public consumption in /pkg. Implementation helpers, adapters, and code you may refactor frequently should remain in /internal or at the root level if module-private but not necessarily secret. This distinction helps you manage semantic versioning by clarifying what constitutes a breaking change.
Where should I place command-line tools in a Go library project?
Place small command-line programs, demos, and utilities in /cmd. Each subdirectory under /cmd should contain a separate main package (e.g., /cmd/myapp/main.go). This keeps the library core separate from executable code while providing a standard location for tooling shipped alongside the library, as recommended in the project's /cmd/README.md.
Does the golang-standards/project-layout work with Go modules?
Yes, the layout is fully compatible with Go modules. Place a single go.mod file at the repository root declaring the module path (e.g., module github.com/user/repo). The module system applies to all subdirectories including /pkg and /internal, and the Go toolchain resolves imports correctly without additional configuration or custom build scripts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →