How to Structure a Go Project

A practical guide to Go project layout - start flat with main.go, split into packages by responsibility, use internal for private code, and avoid layouts copied from other languages.

"How should I structure my Go project?" is one of the most asked questions in the Go community, and the Go Developer Survey lists it as a frequent frustration. There is no single required layout. There is, however, a clear path that the Go team documents on go.dev and that most experienced Go developers follow: start small, and add structure only when the code asks for it.

Stage 1: Everything in the Root

A new command-line tool or small service can live in one folder:

mytool/
  go.mod
  main.go
  config.go
  fetch.go
  fetch_test.go

All files are package main. They share functions and types without imports. Plenty of real tools stay like this for a long time. Many people over-structure early and end up with a dozen folders holding one file each.

Stage 2: Supporting Packages Under internal

When a group of code has its own clear job, such as talking to the database or parsing a file format, move it into a package. For an application, put those packages under internal/:

mytool/
  go.mod
  main.go
  internal/
    store/
      store.go
      store_test.go
    parser/
      parser.go

main.go imports example.com/mytool/internal/store. No outside module can, which keeps you free to change them.

Stage 3: Several Binaries With cmd

When the project produces more than one program, such as a server and a CLI, each gets a folder under cmd/:

shop/
  go.mod
  cmd/
    shop-server/main.go
    shopctl/main.go
  internal/
    order/
    payment/
    store/
  migrations/
  Makefile

The cmd/ folders stay thin: read config, wire dependencies together, start. The real logic lives in internal/.

go build ./cmd/shop-server
go build ./cmd/shopctl

Name Packages by What They Do

The most important layout decision is not the folder tree but how you split packages. Split by domain, the thing the code is about, not by technical layer:

Prefer                     Avoid
internal/order             internal/models
internal/payment           internal/controllers
internal/user              internal/services
                           internal/utils

With domain packages, code reads as order.New, payment.Charge, user.Repository. With layer packages, one feature is spread over four folders and every package imports every other, which often ends in import cycle not allowed.

Avoid utils, common and helpers:

Packages named utils or common grow without limit because anything can go in them, and the name tells the reader nothing at the call site: utils.Format could mean anything. Put each helper next to the code that uses it, or in a package named for what it does, like money or slug.

Layouts Copied From Other Languages

A few habits from other ecosystems do not fit Go:

  • A src/ folder. Go does not need one, and it only lengthens every import path.
  • One folder per class. A Go package can contain many types. Group related types together.
  • Deep trees like internal/app/domain/user/entity. Each level is a package boundary and an import. Keep it shallow.

About golang-standards/project-layout:

A popular GitHub repository named project-layout is often mistaken for an official standard. It is a community collection of patterns, and the Go team does not endorse it as a standard. The official guide is "Organizing a Go module" on go.dev.

Where Tests, Assets and Config Go

  • Tests sit next to the code: store.go and store_test.go in the same folder.
  • Test data goes in a testdata/ folder, which the Go tool ignores when building.
  • SQL migrations, templates and static files can live at the root or under internal/ and be embedded with //go:embed.

Each folder is a package:

Creating folders just to tidy files is the most common layout mistake. Two files in different folders cannot see each other's unexported names, so you end up exporting everything just to make it compile. Only create a folder when you want a separate package.

A healthy layout:

main packages are short, each package in internal/ has a one-word name that describes its job, there are no import cycles, and you can explain where any new code should go in one sentence.

Summary

Start with all files in the root, move code into internal/ packages when a part has its own clear job, and add cmd/ folders when you build more than one binary. Name packages after what they do rather than technical layers, avoid utils and src/, keep the tree shallow, and remember that every folder is a package boundary.