Skip to main content
skill CX Codex

go-project-layout — Claude Code skill: Go project structure

Picks a folder layout for a new Go project: cmd/, internal/, or a flat root for small tools. Useful at the start, before the structure hardens.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add eduardo-sl/go-agent-skills --skill go-project-layout -a claude-code
Open on GitHub

What go-project-layout does

The skill matches the layout to project size. A single-binary tool under five files stays flat in package main at the root. A service gets cmd/<name>/main.go plus packages under internal/. A library keeps its API in the root package and hides helpers in internal/. Empty pkg/, api/ or docs/ directories created ahead of need are discouraged.

It also covers module naming (go mod init github.com/acme/payment-service), a thin main.go that parses arguments and delegates to run, and inward dependency flow: handler → service → domain ← store. Packages take names describing what they provide, so util and common are out. Configuration is loaded in one place and validated at startup.

You need the Go toolchain, and git for repository initialisation. The skill does not review an existing architecture, wire dependencies or set up CI — separate skills in the same collection handle those. A project rule that overrides the layout takes precedence.

What it helps with

  • lay out a new Go service without copying someone else's skeleton
  • decide when a flat root layout is enough
  • move private packages under internal/ so outsiders cannot import them
  • keep main.go thin and testable through run
  • name the module so it can actually be fetched

Example prompts for the agent

  • Scaffold a new Go service: cmd/payment-api/main.go plus packages under internal/
  • I need a folder structure for a small single-binary Go tool — suggest a layout
  • Init the module github.com/acme/payment-service and write a thin main.go that calls run

How to install go-project-layout

1Skills CLI (Vercel)

npx skills add eduardo-sl/go-agent-skills --skill go-project-layout -a claude-code

2Manually: put the files in place

~/.claude/skills/go-project-layout/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/go-project-layout/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about go-project-layout

How do I install go-project-layout in Claude Code?

Run npx skills add eduardo-sl/go-agent-skills --skill go-project-layout -a claude-code. The skill lands in ~/.claude/skills/go-project-layout/SKILL.md.

What does go-project-layout do?

It helps you structure a new Go project: layout by size, conventions for cmd/ and internal/, module naming, and the shape of main.go.

How is it different from go-architecture-review and go-dependency-injection?

This one is for new projects. Reviewing an existing architecture belongs to go-architecture-review, dependency wiring to go-dependency-injection, and CI setup to go-ci.

Source text

SKILL.md · MIT
---
name: go-project-layout
description: >
  Scaffold new Go projects and services: directory structure, cmd/ and
  internal/ conventions, when to use a flat layout, module naming, and main
  package wiring. Use when: "new Go project", "scaffold a service", "create
  project structure", "start a Go module", "how do I organize a new
  service", "set up folder structure".
  Not for: reviewing an existing architecture (go-architecture-review), DI
  wiring (go-dependency-injection), CI setup (go-ci).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain. Requires git for repository initialisation.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*) Bash(git:*)
metadata:
  author: eduardo-sl
  version: "1.2.1"
---

# Go Project Layout

Structure follows size. The biggest layout mistake in Go is copying a
microservice skeleton for a 500-line tool — or growing a 50-package
service inside a flat directory. Match the layout to the project.

> Community default. A project skill or agent-config rule that explicitly
> supersedes `go-project-layout` takes precedence over this one.

## 1. Pick the Layout by Project Size

| Project | Layout |
|---|---|
| Small tool, single binary, <5 files | Flat: everything in package `main` at the root |
| Library for others to import | Root package named after the module, `internal/` for helpers |
| Service with one binary | `cmd/<name>/main.go` + `internal/` packages |
| Multiple binaries sharing code | `cmd/<name1>/`, `cmd/<name2>/` + `internal/` |

Never start with empty `pkg/`, `api/`, `docs/`, `build/` directories
"for later". Add structure when the code demands it, not before.

## 2. Module Naming

```bash
# ✅ Good — repository path, lowercase
go mod init github.com/acme/payment-service

# ❌ Bad — not fetchable, uppercase, or vanity without DNS
go mod init PaymentService
go mod init payment_service
```

The last path element should match what users will see: for a library,
it becomes the default import name.

## 3. Service Layout (the default for APIs and workers)

```text
payment-service/
├── cmd/
│   └── payment-api/
│       └── main.go         # flag/env parsing, wiring, Run() — nothing else
├── internal/
│   ├── domain/             # core types, business rules; zero external deps
│   ├── service/            # use cases orchestrating domain + stores
│   ├── store/              # data access implementations (postgres/, redis/)
│   ├── handler/            # HTTP/gRPC adapters
│   └── config/             # config loading and validation
├── migrations/             # if the service owns a database
├── go.mod
├── Makefile
└── README.md
```

Rules:

- `internal/` by default — the compiler enforces that nobody outside the
  module imports it. Promote to a public package only on demand.
- `pkg/` only when external consumers exist AND the module also has
  private code. When in doubt, don't create it.
- Dependencies point inward: `handler → service → domain ← store`.
  `domain` imports neither `store` nor `handler`.

## 4. Thin main, Runnable Run

Keep `main.go` to wiring plus a delegating call, so the app is testable:

```go
func main() {
    if err := run(context.Background(), os.Args[1:], os.Getenv); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(ctx context.Context, args []string, getenv func(string) string) error {
    cfg, err := config.Load(getenv)
    if err != nil {
        return fmt.Errorf("load config: %w", err)
    }

    db, err := store.Open(ctx, cfg.DatabaseURL)
    if err != nil {
        return fmt.Errorf("open db: %w", err)
    }
    defer db.Close()

    svc := service.New(store.NewUserRepo(db))
    srv := handler.NewServer(cfg.Addr, svc)
    return srv.ListenAndServe(ctx)
}
```

- `os.Exit` appears exactly once, in `main`.
- `run` takes its dependencies (`args`, `getenv`) so tests can call it.
- No `init()` functions for wiring — explicit construction order only.

## 5. Library Layout

```text
retry/
├── retry.go            # package retry — the API, in the root
├── retry_test.go
├── backoff.go          # same package, split by topic
├── internal/
│   └── clock/          # implementation details users must not import
├── examples_test.go    # Example* functions shown in godoc
└── go.mod
```

- The root directory IS the package. No `src/`, no `lib/`.
- One package per concept. Resist `util`, `common`, `helpers` — name
  packages after what they provide (`retry`, `clock`, `httpsign`).

## 6. Naming Rules for Directories and Packages

- Package name == directory name, short, lowercase, no underscores:
  `store/postgres`, not `store/postgres_impl`.
- Don't stutter: `payment.Service`, not `payment.PaymentService`.
- Binary names in `cmd/` are user-facing: `cmd/payment-api`,
  hyphenated is fine (directory only holds package `main`).

## 7. Files That Belong at the Root

- `go.mod`, `go.sum`, `README.md`, `LICENSE`, `Makefile`,
  `.golangci.yml`, `Dockerfile` (single-binary projects).
- Do NOT create: `src/` (un-idiomatic), `vendor/` (unless the team
  explicitly vendors), one-file packages like `types/` or `models/`
  that become dumping grounds.

## 8. Configuration

- One config struct, loaded from the environment in one place
  (`internal/config`), validated at startup — fail fast with every missing
  or invalid value listed, not the first one.
- Flags for CLIs; environment variables for services (12-factor); defaults
  in code. No config files baked into images, no environment-specific
  branches (`if env == "prod"`) in business code.
- Secrets come from the environment or a secret manager, never from flags
  (`ps` shows argv) or the repository.

## 9. One Module or Several

Default to one module per repository. Split into modules only when parts
are versioned and released independently (a public client library next to
a service). Develop across modules locally with `go.work`; don't commit
`replace => ../path` directives.

## Scaffolding Procedure

1. Ask/decide: tool, library, or service? How many binaries?
2. `go mod init <repo-path>`, then set the `go` directive deliberately —
   since Go 1.26, `go mod init` writes the previous release
   (`go 1.(N-1).0`), not the toolchain's.
3. Create only the directories the first feature needs.
4. Write `main.go` with the thin-main pattern above, and a config struct.
5. Pin developer tools as module tools (`go get -tool ...`, Go 1.24+) and
   add `Makefile` targets: `build`, `test`, `lint`.
6. Verify: `go build ./...` and `go vet ./...` pass on the skeleton.

## Verification Checklist

1. Layout matches project size — no empty scaffolding directories
2. Module path is the fetchable repository path
3. All non-public packages live under `internal/`
4. `main.go` is thin: parse, wire, call `run`, exit
5. `os.Exit` only in `main`; no wiring in `init()`
6. Dependencies flow inward; `domain` has zero infrastructure imports
7. No `util`/`common`/`helpers`/`models` grab-bag packages
8. Package names match directories, lowercase, no stutter
9. `go build ./...` passes on the fresh skeleton
10. Config loaded and validated in one place at startup; no secrets in flags
11. One module unless parts are released independently; no committed local `replace`

© eduardo-sl · MIT · eduardo-sl/go-agent-skills

Similar skills

All skills for Programming languages →

From the same set11

git-commit A skill that makes your agent write commit messages in Conventional Commits style: typed, scoped, imperative. Useful when the repo history has turned into noise. CX Codex +1 ★ 83repo stars go-troubleshooting A skill that walks the agent through Go runtime diagnosis: panics, deadlocks, goroutine and memory leaks, race reports. Useful when a program crashes or hangs and the cause is unclear. CX Codex +1 ★ 83repo stars go-observability A skill that sets conventions for Go service telemetry: structured logs with log/slog, OpenTelemetry traces and metrics, SLO alerts. Useful once a service runs in production and you need to see what it does. CX Codex +1 ★ 83repo stars go-error-handling A skill that tells your agent how to create, wrap and inspect Go errors: %w wrapping, sentinel errors, custom types, errors.Is and errors.As. CX Codex +1 ★ 83repo stars go-context A skill that makes your coding agent handle context.Context in Go correctly: cancellation, timeouts, values and the usual anti-patterns. CX Codex +1 ★ 83repo stars go-grpc A skill that steers your coding agent through gRPC work in Go: proto contracts, error codes, deadlines, interceptors and streaming. CX Codex +1 ★ 83repo stars go-dependency-injection A skill that steers your agent toward constructor injection and a single composition root in main. Useful once globals creep in or tests get hard to write. CX Codex +1 ★ 83repo stars go-api-design A skill that steers the agent through Go HTTP handler design: routing, middleware, validation, pagination, graceful shutdown. Useful when you start a REST service or rework one. CX Codex +1 ★ 83repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor