This is a step-by-step tutorial for importing a local package in Go when you are used to copying folders or typing ./something and the compiler complains. You will create a small myapp module, import a greeting subfolder, then see how nested folders, another module with replace, and go.work fit together. For broader layout rules, see Golang packaging, Golang import local package, and create a custom Go module.
Go 1.22+ on Linux;
goversion lines ingo.mod/go.workmay match your installed toolchain.
What we will build
By the end of the main walkthrough you will have this layout:
myapp/
├── go.mod
├── main.go
└── greeting/
└── greeting.goThe rule to remember for the whole page:
import path = module path + package folderConcrete example:
module path: example.com/myapp
folder: greeting
import: example.com/myapp/greetingThat string is what you type in import—not ./greeting, not a file path, and not the word greeting alone.
Step 1: Create a new project directory
mkdir myapp
cd myappThis directory becomes the module root: it will hold go.mod and your packages as subfolders.
Step 2: Initialize a Go module
go mod init example.com/myappYou should see something like go: creating new go.mod: module example.com/myapp.
The module path does not need to be a real website for local learning. In real repos, use the path that matches where the code will live (for example github.com/youruser/myapp). Avoid reusing example.com/... for anything you might publish; that domain is reserved for documentation examples.
go.mod will look like:
module example.com/myapp
go 1.22The exact go 1.xx line follows the Go toolchain on your machine when you run go mod init.
Step 3: Create a local package folder
mkdir greetingThen you will add greeting/greeting.go.
Go imports packages from directories, not individual .go files. Every non-main directory of sources (with the same package clause) is one importable package.
Step 4: Add code to the local package
Create greeting/greeting.go:
package greeting
func Message() string {
return "Hello from local package"
}Two details that prevent the usual beginner errors:
1. The package clause is package greeting (the name you will use in selectors).
2. Message starts with an uppercase letter, so other packages may call greeting.Message().For wider naming guidance, see Go naming conventions.
Step 5: Import the local package from main.go
Create main.go in myapp/ (next to go.mod):
package main
import (
"fmt"
"example.com/myapp/greeting"
)
func main() {
fmt.Println(greeting.Message())
}Important:
The import path is not "./greeting".
The import path is "example.com/myapp/greeting".It is always:
module path from go.mod + "/" + folder path inside the moduleStep 6: Run the program
From myapp/ (where go.mod lives):
go run .Expected output:
Hello from local packagego run . runs the package in the current directory (main here), pulling in your local greeting package by import path. Running a single file path is awkward once multiple packages are involved, so prefer go run . from the module root for this layout.
The sections below explain concepts and fixes you will use right after this walkthrough.
Module path vs package name
Import path:
example.com/myapp/greeting
Package name inside file:
package greeting
Name used in code:
greeting.Message()| Concept | Example | Meaning |
|---|---|---|
| module path | example.com/myapp |
prefix declared in go.mod |
| package folder | greeting |
subdirectory with .go files |
| import path | example.com/myapp/greeting |
module path + folder |
| package name | package greeting |
identifier in selectors (greeting.…) |
| exported function | Message |
visible outside the package |
Exported vs unexported names
If greeting.go used a lowercase function:
package greeting
func message() string {
return "hello"
}then this call in main would not compile:
fmt.Println(greeting.message())Names that start with a lowercase letter are unexported.
They can be used only inside the same package.The fix is to export what callers need:
func Message() string {
return "hello"
}Import a package from a nested subfolder
For searches like “go import package from subfolder,” remember each directory is its own package; nesting only lengthens the import path.
Example layout:
myapp/
├── go.mod
├── main.go
└── internaltext/
└── format/
└── format.go(internaltext is an arbitrary folder name—not the special internal import rule path.)
internaltext/format/format.go:
package format
func Line() string {
return "formatted"
}In main.go:
import "example.com/myapp/internaltext/format"Then call format.Line() (package name format from the package clause).
Every directory is a separate package.
A nested folder creates a longer import path: module path + each segment.When the package name differs from the folder name
Example:
folder: textutil
file: textutil/message.gopackage textImport path (still the folder path):
import "example.com/myapp/textutil"Usage in main uses the package name from the file, not the folder name:
text.Message()Go allows folder name and package name to differ, but beginners should usually keep them aligned to avoid confusion.Import a local package from another module (replace)
Use this when the dependency has its own go.mod (a separate module), not just another folder inside myapp.
workspace/
├── app/
│ ├── go.mod
│ └── main.go
└── mylib/
├── go.mod
└── message.gomylib/go.mod contains module example.com/mylib. mylib/message.go:
package mylib
func Hi() string {
return "from mylib"
}Inside the app/ directory (where that module’s go.mod lives):
go mod edit -require=example.com/mylib@v0.0.0
go mod edit -replace=example.com/mylib=../mylib
go mod tidyThen import the module path, not ../mylib:
import "example.com/mylib"The import path still uses the module path.
The replace directive only tells Go where to find that module on disk.Use go.work for multiple local modules
go.work is useful when you actively develop several modules side by side and want the toolchain to resolve them together without a replace in every consumer go.mod.
workspace/
├── go.work
├── app/
│ └── go.mod
└── mylib/
└── go.modFrom workspace/:
go work init ./app ./mylibA generated go.work looks like:
go 1.22
use (
./app
./mylib
)The go line matches your toolchain version when you run the command.
Use module path + folder for packages inside one module.
Use require+replace or go.work when the dependency is another module with its own go.mod.Common import errors and fixes
Error: package greeting is not in std
Example message:
package greeting is not in std (/usr/local/go/src/greeting)You imported only "greeting" instead of the full module path.Wrong:
import "greeting"Correct:
import "example.com/myapp/greeting"Error: no required module provides package ...
go.mod does not list the other module, or there is no replace/go.work pointing at your local copy.Fix (local sibling module):
go mod edit -require=example.com/mylib@v0.0.0
go mod edit -replace=example.com/mylib=../mylib
go mod tidyError: local import "./greeting" in non-local package
Relative imports are not the normal modules style.Fix:
import "example.com/myapp/greeting"Error: undefined: greeting.message
The function starts with a lowercase letter and is not exported.Fix:
func Message() string {
return "hello"
}Error: imported and not used
You imported the package but never referenced it.Remove the import or use it. For side-effect imports only, use a blank import; see underscore in front of import in Go.
GOPATH vs Go modules
Older tutorials put code under GOPATH/src and imported paths like github.com/user/repo/pkg. Modern Go uses modules: a go.mod at the project root defines the module path, and your project can live anywhere on disk.
You do not need to place your project inside GOPATH to import local packages.go env GOPATH still shows a directory used for caches and some tooling; it is not where your app must live.
Best practices for local packages
- Keep one module per project unless you deliberately split modules.
- Use module path + folder path for packages in the same module.
- Keep package names short and lowercase; export only what other packages need.
- Avoid circular imports.
- Do not try to import individual .go files.
- Use internal/ (the special directory name) for code that must not be imported from outside the parent module tree.
- Use go.work when you routinely edit multiple local modules together.Local package import cheat sheet
| Goal | Use |
|---|---|
| Import a package from a subfolder in the same module | import "module-path/folder" |
| Import a nested subfolder | import "module-path/folder/subfolder" |
| Import another local module | require + replace (and go mod tidy) |
| Work on multiple local modules at once | go work init … |
Import one .go file |
Not supported—use a directory package |
| Call a function from another package | Name must be exported (capitalized) |
Relative import like ./pkg |
Avoid in module projects |
Fix package … is not in std |
Use full module-path/... import |
Fix no required module provides package |
Add require/replace or go.work |
Summary
You built a local package import without GOPATH by initializing go.mod, adding a greeting/ directory, and importing example.com/myapp/greeting. The import path is always module path + folder, not ./folder. Exported names avoid undefined errors; nested folders extend the path. A second module uses require and replace, and go.work ties several local modules together for day-to-day development.

