Back to Blog

How to Structure a Go Project (2026): A Beginner's Guide to Idiomatic Layout

The folder layout most beginners copy from a 50k-star repo is not an official Go standard, and the Go team says so. Here is the real, official approach to structuring a Go project, which is simpler than you expect.

How to Structure a Go Project (2026): A Beginner's Guide to Idiomatic Layout

Search "Go project structure" and within two clicks you land on golang-standards/project-layout, a GitHub repo with more than 50,000 stars and a directory tree full of cmd/, pkg/, internal/, api/, and build/. It looks authoritative. Most beginners copy it wholesale into a five-file program.

Here is the problem: it is not an official standard, and the Go team has said so in public. The real, official guidance is simpler than the popular one, and it is the whole point of Go. You start flat, and you let the layout grow only when the code actually demands it. This guide walks through that official approach, folder by folder, using services you would actually build.

What Is the Standard Go Project Structure?

There is no single mandated tree. Go does not require src/, does not require cmd/, and does not require any particular folder beyond a go.mod file at the module root. The official guide, "Organizing a Go module," is explicit that layout is a function of size. It walks through four layouts, each one a step up from the last: a basic package, a basic command, a package that grows supporting packages, and finally a multi-binary "server" project. (source)

That step-by-step growth is the actual standard. The question is never "which folders does every Go project have" but "how much structure does this project need right now." For most programs the honest answer is: almost none. The rest of this guide walks through those four layouts in order, from the simplest to the most involved, so you can find the one that matches your project and stop there.

Start Flat: One Package Is Idiomatic

The official doc's first two layouts are a single package and a single command, both living directly in the module root. No subdirectories. For a small command-line tool that reads a city and prints the weather, the entire project is this:

example.texttext
weather/
├── go.mod
├── main.go
├── weather.go
└── weather_test.go

go.mod declares the module path (say, module github.com/you/weather). main.go holds package main and the entry point. weather.go holds the logic, in the same package. weather_test.go holds the tests, right next to the code they cover, which is exactly where Go expects them. That is a complete, shippable Go program, and nothing about it is unfinished or amateur. This is what the official doc calls a "basic command," and it is the correct shape for most tools you will write in your first year.

Learn one rule early: directory equals package. Every file in a directory declares the same package, and the package name is the last element of the import path. Add a weather/store/ directory and every file in it says package store, imported as github.com/you/weather/store and called as store.Get. That one rule explains almost every layout decision that follows.

Resist the urge to add folders now. A utils/ directory holding one function, or a models/ directory holding one struct, adds an import path and a package boundary for no benefit. Keep it flat until flatness genuinely hurts. You will know when it does.

When Should You Add an internal/ Directory?

The first real reason to add structure is not organization. It is privacy. Go has exactly two visibility levels for identifiers: capitalized names are exported, lowercase names are package-private. That is fine within a package, but once your module grows several packages, you often want code that any package inside your module can use while nothing outside it can import. That is what internal/ does, and the compiler enforces it.

The rule is a language feature, not a convention: a package under a directory named internal/ can only be imported by code rooted in the parent of that internal/ directory. Import it from another module and the build fails. The official doc recommends keeping packages in internal "as much as possible." The payoff is that outside code can never import them, so it can never depend on your internals and lock you into supporting an API you never meant to promise. (source)

Take that weather CLI and grow it into a small HTTP API for a bookstore. It now has request authentication and a data-access layer, neither of which should be part of a public API:

example.texttext
bookstore/
├── go.mod
├── main.go
└── internal/
    ├── auth/
    │   ├── auth.go
    │   └── auth_test.go
    └── store/
        ├── store.go
        └── store_test.go

Because main.go lives inside the bookstore module, it can import github.com/you/bookstore/internal/auth and github.com/you/bookstore/internal/store with no restrictions. Code in any other module cannot. If someone runs go get on your module and tries to import those packages, the compiler rejects it. That guarantee is what makes internal/ useful: since nobody outside your module can depend on auth or store, you are free to rename their functions, change their signatures, or restructure them whenever you like without breaking anyone else's code.

When Should You Add a cmd/ Directory?

The next step up is for repositories that build more than one binary, or that ship both a binary and an importable library. Until you have that, you do not need cmd/. With a single binary, main.go in the root is cleaner.

The cmd/ convention gives each binary its own subdirectory, where the directory name is the resulting program name. The official "server project" layout uses exactly this: each command lives in cmd/<name>/ with a small main.go that wires together packages from internal/. (source) Suppose the bookstore now ships an API server and a database-migration tool:

example.texttext
bookstore/
├── go.mod
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── migrate/
│       └── main.go
└── internal/
    ├── auth/
    └── store/

go build ./cmd/api produces a binary named api; go build ./cmd/migrate produces migrate. Both main.go files stay thin, importing the real work from internal/.

The table below sums up the whole progression. Find the row that matches your project as it is today and use that layout. Do not jump ahead to a row that describes a bigger project than the one you actually have.

Project shapeRecommended layoutWhy
Small tool or scriptFlat: main.go + logic files in the rootOne package is idiomatic and complete
One binary with reusable internalsAdd internal/ for private packagesCompiler-enforced privacy, no premature API
Two or more binariesAdd cmd/<name>/ per binaryEach main gets its own directory
Multi-service repo (binaries + shared code)cmd/ + internal/, split by domainThe official "server project" layout

The golang-standards/project-layout Myth

The golang-standards/project-layout repo has the word "standards" in its GitHub org name, more than 50,000 stars, and a sprawling tree of api/, build/, configs/, deployments/, pkg/, and test/. It reads like the Go team blessed it. The team did not. The repo's own README now carries a disclaimer that it is not an official standard, and the pushback came from the top.

In issue #117, Russ Cox, a Go tech lead, wrote that this is "not a standard Go project layout," that most Go repositories are "much simpler" and do not use a pkg/ directory at all. The thread reached the front page of Hacker News, and the consensus among experienced Go developers matched Cox: the layout is fine for a large, multi-team monorepo, and actively harmful as a starting template for a normal project. Copying it into a small program buries five files under eight nearly empty folders.

A few concrete rules fall out of this, and they line up with common advice from Go practitioners like Eli Bendersky and Alex Edwards:

  • Do not use a src/ directory. That is a habit carried over from Java and older Node projects. Go code lives at the module root, not under src/. There is no src/ in any official layout.
  • Do not nest deeply before you have the code to fill it. Empty api/, build/, and deployments/ folders are pure ceremony. Add a folder the day you have something to put in it.
  • Name packages by what they provide, not by their role. A package called utils or common or helpers tells a reader nothing and becomes a junk drawer. Name it store, auth, weather, retry, slugify. The official package-names guidance is that a good package name is short, clear, and reads well at the call site, so store.Get beats utils.GetFromStore. (source)

If you want more of these ecosystem-level traps, our guide to common Go mistakes to avoid covers the ones that bite at the code level rather than the folder level.

A Beginner's Rule of Thumb for Go Project Layout

The whole progression comes down to a few questions about what your project actually needs. Work through them from the top and add a directory only when the answer says you need it.

flowchart TD
    A["New Go project"] --> B["Is it one package<br/>of related code?"]
    B -->|Yes| C["Keep it flat:<br/>main.go + logic in the root"]
    B -->|"Grew past that"| D["Need to hide code<br/>from outside importers?"]
    D -->|Yes| E["Add internal/<br/>put private packages there"]
    E --> F["More than one<br/>binary to build?"]
    F -->|No| G["Done. main.go in root,<br/>logic in internal/"]
    F -->|Yes| H["Add cmd/name/<br/>one dir per binary"]
    H --> I["Many services sharing code?"]
    I -->|Yes| J["Server layout:<br/>cmd/ + internal/ by domain"]

Every step is a response to a real pressure: a package boundary you need, a binary you have to build, code you have to hide. Structure that arrives for any other reason is decoration. Start flat, add internal/ when you need enforced privacy, add cmd/ when you have a second binary, and reach for the full server layout only when a genuine multi-service repo forces your hand. That progression is the idiomatic Go project structure.

Frequently Asked Questions

Is there an official Go project structure?

No, there is no single required folder structure. The official document, "Organizing a Go module," describes a layout that grows step by step with project size rather than one fixed structure. It moves from a flat package, to internal/, to cmd/, to a full server layout. Match your project to the smallest layout that fits. (source)

Should I use the pkg/ directory?

Usually not. Russ Cox, a Go tech lead, noted that most Go repositories do not use a pkg/ directory and "tend to be much simpler." Prefer internal/ for private code, which the compiler enforces, and expose packages at the module root only when you intend to support them as public API. (source)

What goes in internal/ versus cmd/?

internal/ holds importable library code that only your module may use, with the privacy enforced by the compiler. cmd/<name>/ holds the thin main package for each binary you build, wiring together code from internal/. Logic lives in internal/; entry points live in cmd/. (source)

Can a real Go project be a single file or package?

Yes. The official layout guide's first example is a single package in the module root, and many production tools ship exactly that way. One package with a go.mod, a main.go, and its tests is idiomatic and complete. Add structure only when the code grows past it. (source)

How is a Go project different from a Java or Node project?

Go has no src/ directory. Code lives at the module root, packages map directly to directories, and the package name is the last path element. There is no build-tool-imposed tree the way Maven or a bundler expects, so a Go layout stays flatter and grows only as needed. (source)

Where to Go From Here

The layout is not the hard part of Go, and that is by design. You ship a real program flat, then let internal/ and cmd/ arrive the day the code demands them. Every folder should answer a pressure you can name. If it cannot, delete it.

The best way to learn it is to build a couple of real programs, a CLI and an HTTP service, and let the structure grow as you go. LevelUpGo's Clean Go Code track covers idiomatic package design and project structure through exercises you write and run in the browser, and Go Fundamentals starts you from your first go.mod and the toolchain. You can also browse the full interactive roadmap.

Ready to master Go?

Join LevelUpGo and start building real projects with interactive, hands-on lessons.

Start Learning Free