Back to Blog

How to Structure a Go Project (2026): Idiomatic Layout Guide

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): Idiomatic Layout Guide

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, and most beginners copy it wholesale into a five-file program.

It is not an official standard, though, and the Go team has said so in public. The official guidance is simpler: start flat, and add folders only when the code needs them. The sections below follow that guidance one folder at a time, using a weather CLI and a bookstore API as the running examples.

What Is the Standard Go Project Structure?

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

So the useful question is how much structure your project needs right now, and for most programs the answer is very little. The rest of this post goes through the four layouts from simplest to most involved. 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 sitting directly in the module root with no subdirectories. For a small command-line tool that reads a city and prints the weather, this is the whole project:

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 where Go expects them. You can ship this as it is. The official doc calls it a "basic command," and it's the right shape for most tools you will write in your first year.

One rule explains most of the layout decisions that come later: a directory is a 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. You import it as github.com/you/weather/store and call it as store.Get.

Don't add folders yet. A utils/ directory holding one function, or a models/ directory holding one struct, adds an import path and a package boundary and gives you nothing back. Keep the project flat until the flatness starts causing real problems.

When Should You Add an internal/ Directory?

The first good reason to add structure is privacy. Go has two visibility levels for identifiers: capitalized names are exported, and lowercase names are private to the package. That works within one package. Once your module has several packages, though, you often want code that every package inside your module can use and nothing outside it can import. That is what internal/ gives you, and the compiler enforces it.

This is part of the language, not a naming convention. A package under a directory named internal/ can only be imported by code rooted in the parent of that internal/ directory, and an import from another module fails to build. The official doc recommends keeping packages in internal "as much as possible." Outside code can't import them, so it can't come to depend on them, and you never end up supporting an API you didn't mean to publish. (source)

Say the weather CLI turns into a small HTTP API for a bookstore. It now has request authentication and a data-access layer, and neither belongs in a public API:

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

main.go lives inside the bookstore module, so it can import github.com/you/bookstore/internal/auth and github.com/you/bookstore/internal/store freely. Code in any other module can't. If someone runs go get on your module and tries to import those packages, the compiler rejects the import. Since nobody outside your module can depend on auth or store, you can rename their functions, change their signatures, or reorganize them whenever you want without breaking anyone else's code.

When Should You Add a cmd/ Directory?

cmd/ is for repositories that build more than one binary, or that ship both a binary and an importable library. If you only have one binary, you don't need it, and main.go in the root is cleaner.

Under cmd/, each binary gets its own subdirectory, and the directory name becomes the program name. The official "server project" layout does 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, and go build ./cmd/migrate produces migrate. Both main.go files stay thin and import the real work from internal/.

Here is the whole progression in one table. Pick the row that matches your project as it is today, not the project you imagine it becoming.

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 "standards" in its GitHub org name, more than 50,000 stars, and a big tree of api/, build/, configs/, deployments/, pkg/, and test/. It's easy to assume the Go team endorsed it, but they didn't. The repo's README now carries a disclaimer saying it is not an official standard, and the objection came from Go's own leadership.

In issue #117, Russ Cox, a Go tech lead, wrote that this is "not a standard Go project layout," and that most Go repositories are "much simpler" and don't use a pkg/ directory at all. The thread made the front page of Hacker News, where experienced Go developers mostly agreed with him. The layout can make sense for a large monorepo shared by several teams, but as a starting template for a normal project it does harm. Copy it into a small program and your five files end up under eight nearly empty folders.

A few rules follow from this, and they match what Go practitioners like Eli Bendersky and Alex Edwards commonly advise:

  • Don't use a src/ directory. That habit comes from Java and older Node projects. Go code lives at the module root, and no official layout has a src/.
  • Don't nest folders before you have code to put in them. Empty api/, build/, and deployments/ folders do nothing. Create a folder the day you have something for it.
  • Name packages for what they provide, not for their role. A package called utils, common, or helpers tells the reader nothing and turns into a junk drawer. Call it store, auth, weather, retry, or slugify. Go's official advice on package names is to keep them short and clear and to make them read well at the call site, so store.Get beats utils.GetFromStore. (source)

For mistakes that show up in the code rather than the folders, see our guide to common Go mistakes to avoid.

A Beginner's Rule of Thumb for Go Project Layout

The progression reduces to a few questions about your project. Go through them from the top, and add a directory only when an answer calls for 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"]

Each step answers a specific need: a package boundary, a second binary, or code you want to keep private. If you can't point to one of those, you probably don't need the folder. Start flat, add internal/ when you want the compiler to enforce privacy, add cmd/ when you have a second binary, and move to the full server layout only when the repo really does hold several services.

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 with project size instead of one fixed structure. It goes from a flat package to internal/, then cmd/, then a full server layout. Use the smallest layout that fits your project. (source)

Should I use the pkg/ directory?

Usually not. Russ Cox, a Go tech lead, noted that most Go repositories don't use a pkg/ directory and "tend to be much simpler." Put private code in internal/, where the compiler enforces the privacy, and expose packages at the module root only when you plan to support them as public API. (source)

What goes in internal/ versus cmd/?

internal/ holds importable library code that only your module can use, and the compiler enforces that. cmd/<name>/ holds the thin main package for each binary you build, which wires together code from internal/. The logic goes in internal/, and the entry points go in cmd/. (source)

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

Yes. The first example in the official layout guide is a single package in the module root, and plenty of production tools ship that way. One package with a go.mod, a main.go, and its tests is idiomatic and complete. Add structure when the code outgrows 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, each directory is a package, and the package name is the last element of the path. No build tool imposes a tree the way Maven or a bundler does, so Go layouts stay flatter and grow only when needed. (source)

Where to Go From Here

Layout is one of the easier parts of Go. You ship a program flat, then add internal/ and cmd/ when the code needs them. If you can't say why a folder exists, you can probably delete it.

The best way to get a feel for this is to build a couple of real programs, say 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.

Write Go like a senior engineer

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

Try a free lessonOr create a free account