Go has no framework-imposed folder structure, which is freeing and confusing at once. This guide shows a practical, idiomatic layout that scales from a tiny tool to a real service, and explains the trade-offs behind each choice.
Start with a module
Every modern Go project is a module. Initialise one with a module path, usually your repository URL:
go mod init github.com/yourname/greet
This creates a go.mod file that records the module path and Go version, and
lists your dependencies. When you add a dependency, Go also writes a go.sum
file with cryptographic checksums so builds are reproducible. Commit both files;
never edit them by hand.
Keep it flat while you can
The most common mistake is over-organising too early. A small program is fine as a handful of files in one package at the repository root:
greet/
βββ go.mod
βββ go.sum
βββ main.go
βββ greeting.go
Only add directories when a real need appears β a second binary, or code you want to hide from importers. Premature nesting just adds import paths to type.
The main package and entry point
The executable lives in package main with a func main entry point. Keep
main thin: parse configuration, wire dependencies together, and hand off to
your real logic in another package.
// Package main is the entry point of the greet command.
package main
import (
"fmt"
"github.com/yourname/greet/internal/greeting"
)
func main() {
fmt.Println(greeting.Message("Go"))
}
internal/ for private code
Go enforces one layout rule with the compiler: code under an internal/
directory can only be imported by packages rooted at internal/βs parent. It is
the idiomatic way to keep implementation details private to your module.
greet/
βββ go.mod
βββ main.go
βββ internal/
β βββ greeting/
β βββ greeting.go
βββ cmd/
βββ greet/
βββ main.go
cmd/ and pkg/: the debate
Two conventional directories are widely used but often misapplied.
cmd/holds one subdirectory per binary, each with its ownmain.go. It earns its place as soon as you have more than one executable. With a single binary, a top-levelmain.gois simpler.pkg/is meant for code you intend other projects to import. It is contested: many maintainers argue it adds a directory level with no compiler meaning, unlikeinternal/. Preferinternal/for private code, and only reach forpkg/when you deliberately publish a reusable library.
Naming
- Package names are short, lowercase, and single-word:
greeting, notgreetingUtils. The directory name should match the package name. - Avoid stutter: a function in package
greetingshould begreeting.Message, notgreeting.GreetingMessage. - There is no
utilsorhelpersgrab-bag package in idiomatic Go. Name packages after what they provide.
For the official reference on modules and package design, see the documentation at go.dev, which is the source of truth as the toolchain evolves.
Summary
- Every project is a module: commit
go.modandgo.sum, never edit by hand. - Stay flat early; add
internal/andcmd/only when a real need appears. - Prefer
internal/overpkg/for privacy, and name packages for what they do.
Next, tighten your style with the Go conventions guide, or follow the Go developer roadmap for the bigger picture.