API Design Rules for Go Libraries
Naming, error returns, context first param, and breaking changes.
Search across all documentation pages
Naming, error returns, context first param, and breaking changes.
Dense reference for authors publishing Go libraries - scan before exporting new symbols or tagging a release.
go vet, staticcheck API checks, and semver tags.| Rule | Do | Avoid | Rationale |
|---|---|---|---|
| Package name | Short, lowercase, no underscores (httpclient) | common, util, misc | Import path readability |
| Exported name | MixedCaps, no stutter (user.UserID) | user.UserUserID | pkg.go.dev clarity |
| Getter | Name() not GetName() | Java-style Get* on simple fields | Go convention |
| Interface name | -er suffix when one method (Reader) | IReader | Idiomatic interfaces |
| Acronyms | Consistent casing (HTTP, ID, URL) | Http, Id | golint/revive alignment |
| Error vars | Err prefix exported sentinels | ErrorNotFound mixed styles | errors.Is ergonomics |
| Rule | Do | Avoid | Rationale |
|---|---|---|---|
| Context | func F(ctx context.Context, ...) first | context mid-params | Cancellation propagation |
| Options | Functional options or config struct | Long positional param lists | Forward-compatible APIs |
| Returns | Concrete structs/pointers | Unexported-interface returns | Caller type clarity |
| Parameters | Small interfaces at call site | Export wide Interface in producer pkg | Interface segregation |
| Nil | Document nil behavior | Panic on nil without doc | Predictable libraries |
| Time | time.Time / time.Duration in APIs | Raw int64 without units | Ambiguity in public APIs |
| Rule | Do | Avoid | Rationale |
|---|---|---|---|
| Errors | Return error last | Panic for expected failures | Caller control |
| Wrapping | fmt.Errorf("op: %w", err) | Opaque errors.New chains | Inspection with Is/As |
| Sentinels | Stable var ErrX = errors.New(...) | String compare on Error() | Fragile clients |
| Logging | Let application log | Library log.Printf on errors | Import coupling |
| Metrics | Optional hooks/interfaces | Hard-coded Prometheus | Dependency weight |
| Rule | Do | Avoid | Rationale |
|---|---|---|---|
| Semver tag | v1.2.3 on module tags | Ad-hoc git tags | go get resolution |
| Breaking change | Major bump / new module path | Rename exports in patch | Consumer breakage |
| Deprecated | // Deprecated: use X + issue | Silent removal | Migration time |
| Experimental | v0 or internal/ until stable | v1 with churn | Expectation setting |
| go.mod | go directive matches CI | Drift across modules | Toolchain consistency |
| Rule | Do | Avoid | Rationale |
|---|---|---|---|
| Godoc | Full sentences on exports | Empty or wrong-name comments | pkg.go.dev quality |
| Examples | Example* in example_test.go | README-only samples | Compile-checked docs |
| Tests | package foo_test for consumer view | Only white-box tests | API ergonomics signal |
| Build tags | Document integration build tags | Surprise //go:build in core API | Consumer go test |
// Preferred: concrete return, optional functional options
type Client struct { /* ... */ }
type Option func(*Client)
func WithTimeout(d time.Duration) Option { /* ... */ }
func New(opts ...Option) (*Client, error) {
c := &Client{timeout: 30 * time.Second}
for _, o := range opts {
o(c)
}
return c, nil
}// Accept interfaces at integration boundaries
func RegisterHandler(mux ServeMux, h Handler) { /* ... */ }| Change | Semver impact | Example |
|---|---|---|
| Unexported field added | Patch | Safe |
| New exported func | Minor | func ParseConfig |
| Exported func signature change | Major | Parameter type change |
| Remove exported symbol | Major | Delete LegacyDial |
| Behavior fix breaking callers | Major or feature flag | Stricter validation |
Rarely.
Return concrete types unless you intentionally hide implementation behind a small interface type you define.
No.
Accept ctx from callers; only top-level main or tests create roots.
Often one method.
Consumers define them; producers return structs.
Use sentinel var ErrFoo values.
Stable inspection beats substring matching on messages.
Options scale for libraries with many defaults.
Config structs work when fields are mostly required.
Use internal/ until stable, or stay on v0 module tags with clear README warnings.
Put them in package foo_test or internal/testutil.
Avoid widening semver surface for test-only helpers.
Export clear type parameters with constraints.
Avoid exporting overly generic helpers that obscure types.
Almost never on exported names.
Package name already provides context (user.New not user.NewUser).
Add Deprecated godoc, keep symbol, ship replacement, remove in next major.
Libraries should use ctx for cancellation/deadlines.
Avoid requiring private context keys unless documented as optional extension points.
staticcheck SA1019 (deprecated), exported comment checks (revive), and vet struct tags.
Pair with human review for shape.
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).
Reviewed by Chris St. John·Last updated Jul 16, 2026