The internal Directory in Go
How Go's internal directory restricts which packages can import code, the use of internal package not allowed error, and when to use internal instead of pkg.
Capital letters control which names a package exposes. The internal directory controls something bigger: which other packages are allowed to import a package at all. It is enforced by the go command, not just a naming convention.
The Rule
A package inside a folder named internal can only be imported by code rooted at the parent of that internal folder.
example.com/shop/
go.mod
cmd/shop/main.go can import shop/internal/price (yes)
internal/price/price.go
api/handlers.go can import shop/internal/price (yes)
example.com/other/main.go cannot import shop/internal/price
Everything under example.com/shop/ may use example.com/shop/internal/price. Code in a different module, or outside the shop tree, gets a build error.
The Error You See
If another module tries to import it:
// file: other/main.go (module example.com/other)
package main
import (
"fmt"
"example.com/shop/internal/price"
)
func main() {
fmt.Println(price.WithTax(1))
}
the build fails with:
package example.com/other
main.go:6:2: use of internal package example.com/shop/internal/price not allowed
It does not matter that WithTax is exported. The whole package is off-limits from outside.
Why internal Is Useful
When you publish a module, every importable package becomes part of what other people can depend on. If someone imports a helper package you meant only for yourself, you can no longer rename or delete it without breaking them.
Putting that code under internal/ means you can change it freely. It is the safe default for application code: handlers, database code, configuration, anything that is not meant to be a library.
internal can appear at any level:
The rule works for nested folders too. shop/api/internal/auth can be imported by anything under shop/api/ but not by shop/cmd/. Use deeper internal folders to hide helpers that only one part of the project should use.
internal vs pkg
Many older projects use a pkg/ folder for library code, from a popular community layout repository. pkg has no special meaning to Go. Packages under pkg/ are as public as any other.
shop/pkg/price importable by anyone
shop/internal/price importable only inside shop/
For applications, prefer internal/. Only put packages at the top level, or in pkg/ if you like that convention, when you intend other modules to import them.
Moving code out of internal is a commitment:
Moving a package from internal/ to a public path is easy. Moving it back breaks everyone who started importing it. Start in internal/ and promote packages only when there is a real outside user.
A well-fenced project:
If everything except your intended public API lives under internal/, you can refactor the inside of your project without worrying about breaking anyone.
Summary
Packages under an internal folder can only be imported by code rooted at that folder's parent, and violations fail with use of internal package ... not allowed. This lets you keep application code private even when the repository is public. pkg/ has no special meaning, so use internal/ by default and only make packages importable when you mean to support outside users.