go-project-layout
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).
How do I install this agent skill?
npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-project-layoutIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides standard Go project scaffolding templates and organization guidelines. It enforces tool restrictions and follows idiomatic Go practices without introducing security vulnerabilities.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
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-layouttakes 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
# ✅ 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)
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.domainimports neitherstorenorhandler.
4. Thin main, Runnable Run
Keep main.go to wiring plus a delegating call, so the app is testable:
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.Exitappears exactly once, inmain.runtakes its dependencies (args,getenv) so tests can call it.- No
init()functions for wiring — explicit construction order only.
5. Library Layout
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/, nolib/. - 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, notstore/postgres_impl. - Don't stutter:
payment.Service, notpayment.PaymentService. - Binary names in
cmd/are user-facing:cmd/payment-api, hyphenated is fine (directory only holds packagemain).
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 liketypes/ormodels/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
(
psshows 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
- Ask/decide: tool, library, or service? How many binaries?
go mod init <repo-path>, then set thegodirective deliberately — since Go 1.26,go mod initwrites the previous release (go 1.(N-1).0), not the toolchain's.- Create only the directories the first feature needs.
- Write
main.gowith the thin-main pattern above, and a config struct. - Pin developer tools as module tools (
go get -tool ..., Go 1.24+) and addMakefiletargets:build,test,lint. - Verify:
go build ./...andgo vet ./...pass on the skeleton.
Verification Checklist
- Layout matches project size — no empty scaffolding directories
- Module path is the fetchable repository path
- All non-public packages live under
internal/ main.gois thin: parse, wire, callrun, exitos.Exitonly inmain; no wiring ininit()- Dependencies flow inward;
domainhas zero infrastructure imports - No
util/common/helpers/modelsgrab-bag packages - Package names match directories, lowercase, no stutter
go build ./...passes on the fresh skeleton- Config loaded and validated in one place at startup; no secrets in flags
- One module unless parts are released independently; no committed local
replace
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/eduardo-sl/go-agent-skills/go-project-layout">View go-project-layout on skillZs</a>