Import Local Package in Go Without GOPATH: Step-by-Step Go Modules Example

Learn how to import a local Go package without GOPATH using go.mod, module paths, subfolders, exported names, replace directives, and go.work.

Published

Updated

Read time 8 min read

Reviewed byDeepak Prasad

Import Local Package in Go Without GOPATH: Step-by-Step Go Modules Example

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; go version lines in go.mod / go.work may match your installed toolchain.


What we will build

By the end of the main walkthrough you will have this layout:

text
myapp/
├── go.mod
├── main.go
└── greeting/
    └── greeting.go

The rule to remember for the whole page:

text
import path = module path + package folder

Concrete example:

text
module path: example.com/myapp
folder:      greeting
import:      example.com/myapp/greeting

That 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

bash
mkdir myapp
cd myapp

This directory becomes the module root: it will hold go.mod and your packages as subfolders.


Step 2: Initialize a Go module

bash
go mod init example.com/myapp

You 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:

text
module example.com/myapp

go 1.22

The 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

bash
mkdir greeting

Then 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:

go
package greeting

func Message() string {
	return "Hello from local package"
}

Two details that prevent the usual beginner errors:

text
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):

go
package main

import (
	"fmt"

	"example.com/myapp/greeting"
)

func main() {
	fmt.Println(greeting.Message())
}

Important:

text
The import path is not "./greeting".
The import path is "example.com/myapp/greeting".

It is always:

text
module path from go.mod + "/" + folder path inside the module

Step 6: Run the program

From myapp/ (where go.mod lives):

bash
go run .

Expected output:

text
Hello from local package

go 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

text
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:

go
package greeting

func message() string {
	return "hello"
}

then this call in main would not compile:

go
fmt.Println(greeting.message())
text
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:

go
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:

text
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:

go
package format

func Line() string {
	return "formatted"
}

In main.go:

go
import "example.com/myapp/internaltext/format"

Then call format.Line() (package name format from the package clause).

text
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:

text
folder: textutil
file:   textutil/message.go
go
package text

Import path (still the folder path):

go
import "example.com/myapp/textutil"

Usage in main uses the package name from the file, not the folder name:

go
text.Message()
text
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.

text
workspace/
├── app/
│   ├── go.mod
│   └── main.go
└── mylib/
    ├── go.mod
    └── message.go

mylib/go.mod contains module example.com/mylib. mylib/message.go:

go
package mylib

func Hi() string {
	return "from mylib"
}

Inside the app/ directory (where that module’s go.mod lives):

bash
go mod edit -require=example.com/mylib@v0.0.0
go mod edit -replace=example.com/mylib=../mylib
go mod tidy

Then import the module path, not ../mylib:

go
import "example.com/mylib"
text
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.

text
workspace/
├── go.work
├── app/
│   └── go.mod
└── mylib/
    └── go.mod

From workspace/:

bash
go work init ./app ./mylib

A generated go.work looks like:

text
go 1.22

use (
	./app
	./mylib
)

The go line matches your toolchain version when you run the command.

text
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:

text
package greeting is not in std (/usr/local/go/src/greeting)
text
You imported only "greeting" instead of the full module path.

Wrong:

go
import "greeting"

Correct:

go
import "example.com/myapp/greeting"

Error: no required module provides package ...

text
go.mod does not list the other module, or there is no replace/go.work pointing at your local copy.

Fix (local sibling module):

bash
go mod edit -require=example.com/mylib@v0.0.0
go mod edit -replace=example.com/mylib=../mylib
go mod tidy

Error: local import "./greeting" in non-local package

text
Relative imports are not the normal modules style.

Fix:

go
import "example.com/myapp/greeting"

Error: undefined: greeting.message

text
The function starts with a lowercase letter and is not exported.

Fix:

go
func Message() string {
	return "hello"
}

Error: imported and not used

text
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.

text
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

text
- 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.


References


Frequently Asked Questions

1. How do I import a local package in Go without GOPATH?

Follow Go modules: go mod init sets your module path; put each package in its own directory under the module root; import with "/" for same-module code, or require+replace or go.work for another local module with its own go.mod.

2. Can I import a single .go file by path?

No. Go imports packages (directories of .go files that share a package clause), not individual files.

3. What is the difference between replace and go.work?

replace pins a module version to a directory inside go.mod (good for one app depending on a local fork). go.work groups several local modules for development without editing each module go.mod.

4. Why do I see package greeting is not in std?

You probably imported "greeting" instead of the full path such as example.com/myapp/greeting. Imports must start with your module path (or another module path), not a bare folder name.

5. Why is my local function undefined from another package?

Lowercase names are unexported. Capitalize the first letter of functions and types you need to call from other packages.

6. Is GOPATH still required for local imports?

No. Use a go.mod at the project root; your project can live outside GOPATH. go env GOPATH still exists for cache and legacy tooling, but module mode is the default workflow.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …