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.