Back to Blog

The Go package Keyword: package main, Naming Rules and Examples

Every Go file starts with a package clause. Learn what package main does, how package names map to directories and import paths, and Go's naming rules.

The Go package Keyword: package main, Naming Rules and Examples

The package keyword names the package a Go source file belongs to, and it has to be the first line of code in every .go file. package main builds an executable program, and any other name, such as package billing, builds a library that other packages import. All files in one directory must use the same package name, and that name is what other code writes before a dot to reach your exported names, as in billing.Total (Go spec).

TL;DR

  • Every .go file starts with package name. Only comments and blank lines can come before it.
  • package main is the program. It needs a func main() with no arguments and no results, and go build turns it into a binary. It cannot be imported.
  • One directory is one package. Files with different package names in the same folder fail with found packages billing (invoice.go) and payments (tax.go). The main exception is _test packages in test files.
  • The package name and the import path are different things. You import "gopkg.in/yaml.v3" and call yaml.Unmarshal. By convention the name matches the last element of the path.
  • Good package names are short, lowercase single words, without underscores or mixedCaps. Avoid util and common.
  • Visibility is decided per package. A capitalized name is exported to other packages. A lowercase name is visible to every file in the same package and nowhere else.

What does package main do in Go?

package main tells the Go toolchain to build an executable instead of a library. The main function in that package is where the program starts, and when it returns the program exits. Here is a complete HTTP service in one file:

example.gogo
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "ok")
	})
	log.Fatal(http.ListenAndServe(":8080", mux))
}

go run . compiles and runs it, and go build -o bin/api . writes a binary. The toolchain enforces both rules around main. A package main without a main function fails to link:

example.texttext
runtime.main_main·f: function main is undeclared in the main package

And running a library package with go run ./billing fails before anything compiles:

example.texttext
package example.com/shop/billing is not a main package

Because main is the program, no other package can import it. Trying gives import "example.com/shop/cmd/api" is a program, not an importable package. So Go projects keep their logic in library packages and leave main small. A repository with several binaries puts each one in its own directory, usually under cmd/, and every one of those directories is a separate package main. LevelUpGo's own backend has 11 of them, from the API server to the content importer, and they all share 22 library packages under internal/.

Can two files in one directory have different package names?

No. The go command treats a directory as one package, so every .go file in it must declare the same name. Say billing/invoice.go starts with package billing and someone adds billing/tax.go with package payments. go build ./billing stops with:

example.texttext
found packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing

The fix is to rename one of the clauses or move the file to its own directory. The directory is also the smallest unit you can import, so if two files belong to different packages, they need different folders.

Splitting one package across several files costs nothing. Every file in the directory sees every other file's names, exported or not, without importing anything. A billing package with invoice.go, tax.go and refund.go behaves exactly like one long file.

What is the difference between a package name and an import path?

The import path is where the package lives, and the package name is the identifier you use in code. The import path is the module path from go.mod plus the directory, like example.com/shop/billing. The package name comes from the package clause in the files inside that directory.

By convention they line up, so the name is the last element of the path. The table shows one path where they match and the two usual exceptions, both caused by versions:

Import pathPackage nameWhy they differ
net/httphttpThey match
gopkg.in/yaml.v3yamlThe path carries a .v3 version suffix
math/rand/v2randMajor version directory /v2
github.com/jackc/pgx/v5pgxMajor version directory /v5

When two imports share a name, you rename one in the import block. Code that creates session tokens and also adds retry jitter often needs both crypto/rand and math/rand/v2, which are both called rand:

example.gogo
package tokens

import (
	"crypto/rand"
	"encoding/hex"
	mrand "math/rand/v2"
	"time"
)

// NewSessionID returns a random 32-character hex session ID.
func NewSessionID() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// RetryDelay adds up to 100ms of jitter to a base delay.
func RetryDelay(base time.Duration) time.Duration {
	return base + time.Duration(mrand.IntN(100))*time.Millisecond
}

The rename only applies inside that file. Every other file still sees the package's declared name. The Go import keyword covers aliases, blank imports and the other import forms.

A directory can hold a package whose name differs from the folder, and Go will compile it. The cost falls on readers. Someone who imports example.com/shop/httpapi-v2 has to open the source to learn that the package is called httpapi. Keep them equal unless a version suffix forces a difference.

How should you name a Go package?

Go package names follow a short set of rules from Effective Go and the Go blog's Package names article:

  • Lowercase, one word: billing, httpapi, ratelimit. Not rate_limit or rateLimit. The spec allows them because a package name is any identifier, but idiomatic Go code doesn't use them.
  • Short and specific. The name is typed at every call site. strconv beats stringconversion, and auth beats authenticationservice.
  • Named after what it provides. util, common, helpers, misc and models say nothing about what is inside, and they grow into packages that everything imports. Split them by purpose instead, like money, slugify or pagination.
  • Don't repeat the package name in exported names, because callers already write it. http.Server is right and http.HTTPServer is not. billing.Total is right and billing.BillingTotal is not. A constructor for a package's main type is often just New, as in ring.New.
  • Not a keyword or _. package type is a syntax error and package _ fails with invalid package name _.

Read the name together with its exported identifiers, since that is how every caller sees it. ratelimit.New(100, time.Minute) and config.Load("app.yaml") read like sentences. utils.NewRateLimiterUtil does not.

How does package control visibility in Go?

Go has no public, private or protected keywords. The package is the boundary instead. A name that starts with an uppercase letter is exported and visible to any package that imports it. A lowercase name is unexported and visible only inside its own package, across all of its files.

example.gogo
// Package billing calculates invoice totals and applies VAT.
package billing

// VATPercent is the standard VAT rate applied to invoices.
const VATPercent = 25

// Total returns the sum of line items in cents, including VAT.
func Total(lineItems []int64) int64 {
	var sum int64
	for _, c := range lineItems {
		sum += c
	}
	return addVAT(sum)
}

func addVAT(cents int64) int64 {
	return cents + cents*VATPercent/100
}

A handler in package main can call billing.Total, but billing.addVAT(1000) fails with name addVAT not exported by package billing. The same rule applies to struct fields and methods. encoding/json can only see a struct's exported fields for the same reason.

For code that several of your own packages share but outside modules should not import, Go has internal directories. A package under example.com/shop/internal/pricing can be imported by anything rooted at example.com/shop, and nothing else. Another module that tries gets:

example.texttext
use of internal package example.com/shop/internal/pricing not allowed

Go project structure covers when to reach for internal/ and how to lay out cmd/ and library packages in a real service.

What is a _test package in Go?

Test files, the ones ending in _test.go, can use one of two package names. package billing puts the test inside the package, so it can call unexported functions:

example.gogo
package billing

import "testing"

func TestAddVAT(t *testing.T) {
	if got := addVAT(1000); got != 1250 {
		t.Fatalf("addVAT(1000) = %d, want 1250", got)
	}
}

package billing_test makes it an external test package. It lives in the same directory, but it has to import billing like any other caller and can only use exported names:

example.gogo
package billing_test

import (
	"testing"

	"example.com/shop/billing"
)

func TestTotal(t *testing.T) {
	got := billing.Total([]int64{1000, 2000})
	if got != 3750 {
		t.Fatalf("Total = %d, want 3750", got)
	}
}

Apart from files that a build constraint excludes, such as a //go:build ignore generator script, the _test suffix is the only way two package names can share a directory. go test builds the two packages separately. External tests check the API the way callers use it, and they are also how the standard library writes runnable Example functions that show up in the docs. Many Go codebases mix both kinds, with in-package tests for tricky internals and _test packages for the public behavior.

What can come before the package clause?

Only comments and blank lines. Two kinds of comment in that spot mean something to the toolchain.

A package doc comment sits directly above package with no blank line in between. By convention it starts with Package billing, and go doc and pkg.go.dev show it as the package's documentation. Large packages often put it in a file called doc.go that contains nothing else. For a package main command, the comment describes the program instead, as in // Api serves the shop's HTTP API.

A build constraint decides whether the file is compiled at all. It must come before the package clause and be followed by a blank line:

example.gogo
//go:build debug

package billing

import "log"

func init() {
	log.Println("billing: debug build")
}

This file only compiles with go build -tags debug. Files named with an OS or architecture suffix, like watch_linux.go or asm_arm64.go, get the same treatment from their filename without any comment.

The example also shows init. A package can declare any number of init functions, even several in one file. Go runs them after the package-level variables are initialized and before anything that imports the package starts. An imported package is fully initialized before the package that imports it, so main always runs last. Keep init for cheap setup like registering a driver. Anything that can fail belongs in a function that returns an error.

Where LevelUpGo fits

LevelUpGo teaches Go through exercises that run real Go code in the browser. The Packages & Organization course covers exported names, init and package state, Go modules and project layout, and Code Organization shows how to split a growing codebase into packages with clear names. If you are just starting, Go Basics comes first. For the other 24 reserved words, see Go keywords: all 25 explained.

FAQ

Can a Go package name be different from its directory name?

Yes. The compiler only reads the package clause, so package httpapi inside a folder called httpapi-v2 builds fine. The convention is that the name matches the last element of the import path, and the main exceptions are version suffixes like gopkg.in/yaml.v3 (package yaml) and math/rand/v2 (package rand).

Why does a Go program need package main?

The go command builds an executable only from a package named main, and the runtime starts the program by calling that package's main function. Every other package is a library. A package main without func main() fails with function main is undeclared in the main package.

Can a Go module have more than one package main?

Yes, one per directory. A service with an API server and a migration tool usually has cmd/api/main.go and cmd/migrate/main.go, both declaring package main. You build or run each one by its directory: go run ./cmd/api or go build ./cmd/migrate.

Can a Go package name have underscores or capital letters?

The compiler allows both, because a package name is any valid identifier except _. Go style says not to use them. The Go blog's Package names article asks for short, lowercase names "with no under_scores or mixedCaps," like strconv or httputil.

Is package a keyword in Go?

Yes. package is one of Go's 25 reserved keywords, so you cannot use it as a variable or function name. Go code that needs a variable for a package usually calls it pkg.

Sources

Write Go like a senior engineer

Interactive lessons in your browser. The first ones are free.

Try a free lessonOr create a free account