Go Naming Conventions That the Whole Ecosystem Follows
A lookup table of Go naming rules for packages, files, types, constants, acronyms, getters, receivers, errors and tests, with the standard library as proof.

This is the reference to keep open while you write Go. None of these rules is checked by the compiler, except that a leading capital exports a name, and all of them are followed by the standard library and by nearly every library you will import. Following them is how your code reads like everyone else's. The rules are listed first, then the cases people get wrong, and the program at the end uses each one. The reasoning for packages is in package naming, and the wider start is the Go basics path.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
The table
| Thing | Rule | Example |
|---|---|---|
| Package | Short, lowercase, no underscores, singular | bytes, user, httputil |
| Source file | Lowercase, underscores allowed, words by purpose | user_store.go, server.go |
| Test file | _test.go suffix | user_store_test.go |
| Platform file | _GOOS, _GOARCH or both before .go | sock_linux.go, asm_arm64.go |
| Exported name | Starts with an uppercase letter, MixedCaps | ParseConfig, MaxRetries |
| Unexported name | Starts with a lowercase letter, mixedCaps | parseConfig, maxRetries |
| Acronyms | One case throughout | ID, URL, HTTP; userID, ServeHTTP, xmlParser |
| Constants | MixedCaps, not ALL_CAPS | DefaultTimeout, maxSize |
| Getter | The field name, no Get | u.Owner() |
| Setter | Set plus the field name | u.SetOwner(x) |
| One-method interface | Method name plus er | Reader, Writer, Stringer |
| Receiver | One or two letters from the type, same in every method | func (s *Server), func (c *Cache) |
| Local variable | Short in a short scope, longer in a long one | i, n, buf; pendingRequests |
| Error variable | Err prefix | ErrNotFound, io.EOF is the old exception |
| Error type | Error suffix | *PathError, ParseError |
| Error string | Lowercase, no final punctuation | "open config: file not found" |
| Test function | TestName, BenchmarkName, ExampleName, FuzzName | TestParseConfig |
| Type parameter | Short uppercase, T, K, V | func Map[T, U any](...) |
| Build tag | go:build line, lowercase | //go:build linux && amd64 |
The ones people get wrong
Acronyms
userId and ServeHttp look natural to people who learned in other languages. In Go an initialism stays in one case: all capitals when exported or at the start of a word, all lowercase when the word begins an unexported name. So userID, ID, ServeHTTP, httpClient, parseURL, HTMLEscape. The standard library has a handful of old exceptions, and the rule for new code is firm. Linters such as revive and staticcheck flag the wrong form.
Getters
Fields that need an accessor use the field's own name for the getter. owner the field and Owner() the method, with SetOwner() for the setter if one exists. A GetOwner method adds a word that carries no information, and it is the habit most often carried over from Java. The Get prefix is fine where it means "go and fetch", as in an HTTP Get or a cache Get(key), because the operation is not a field read.
Receivers
A receiver name is a short abbreviation of the type, the same in every method of the type: s for Server, c for Cache, rw for ReadWriter. Not self, not this, and not different letters in different methods. It stays short because the receiver appears in nearly every line of a method body, and a long name would swamp the logic.
Underscores
Do not use them in Go identifiers, with the exception of test function names that include a subtest style like TestParse_empty, which some teams allow, and generated code. Constants are MaxRetries and not MAX_RETRIES. File names may contain underscores, and a file name suffix of _test, _linux and so on has meaning to the toolchain.
Variable name length
The rule is proportional: the bigger the scope, the longer the name. A loop index in a three-line loop is i. A parameter of a short function is n or s. A package-level variable is a full word. A variable that lives across fifty lines deserves a name that reminds the reader what it is. The idioms that every Go reader knows are err, ok, ctx, i, j, n, b for bytes or a benchmark, t for a test, r for a reader, w for a writer, buf, s for a string, m for a map.
Errors
Sentinel values are var ErrNotFound = errors.New("not found"). Custom error types end in Error, and the pointer to a struct, *PathError, is the usual shape. The string of an error is lowercase and unpunctuated, because it is meant to be joined to others in a chain, "open /etc/app.toml: permission denied", and a capital letter or full stop in the middle of a chain reads wrong. Error message style in Go goes through the cases.
A program that follows them
This program uses most of the rows above, so you can read the rules back against real code:
package main
import (
"errors"
"fmt"
)
const DefaultLimit = 3
var ErrLimitReached = errors.New("limit reached")
type Account struct {
owner string
userID int
items []string
}
func NewAccount(owner string, userID int) *Account {
return &Account{owner: owner, userID: userID}
}
func (a *Account) Owner() string { return a.owner }
func (a *Account) SetOwner(o string) { a.owner = o }
func (a *Account) UserID() int { return a.userID }
func (a *Account) Add(item string) error {
if len(a.items) >= DefaultLimit {
return fmt.Errorf("add %q: %w", item, ErrLimitReached)
}
a.items = append(a.items, item)
return nil
}
type Describer interface {
Describe() string
}
func (a *Account) Describe() string {
return fmt.Sprintf("%s (id %d) holds %d items", a.owner, a.userID, len(a.items))
}
func main() {
a := NewAccount("ada", 7)
for _, it := range []string{"a", "b", "c", "d"} {
if err := a.Add(it); err != nil {
fmt.Println(err, errors.Is(err, ErrLimitReached))
}
}
var d Describer = a
fmt.Println(d.Describe(), a.Owner())
}add "d": limit reached true
ada (id 7) holds 3 items adaRead it with the table beside it: the exported constant and error, userID and UserID with the acronym in one case, getters with no Get, the setter with Set, a receiver letter that is the same in every method, a one-method interface named Describer after its method, and an error string that is lowercase and punctuation-free so that the wrapped message reads as one line.
What is not on this list, and why
Naming in tests has more room: table-driven test case names are free-form strings, and helper functions in _test.go files follow ordinary rules. Generated code, such as protocol buffer output, follows its generator's conventions and is exempt from your linter. Names inside a package that mirrors an external schema, a JSON field user_id or a column created_at, belong in struct tags, not in the Go identifier: the field is UserID and the tag is json:"user_id". And there are conventions about names that are really about design, such as avoiding stutter or generic names, which the package naming article and Effective Go treat in depth. The authoritative short list is the Go Code Review Comments, and gofmt will fix your layout but not your names, so run a linter for those.


