Skip to content

Fix: Go undefined: variable (or function)

FixDevs · (Updated: )

Part of:  Go, Rust & Systems Errors

Quick Answer

How to fix Go undefined error caused by undeclared variables, wrong package scope, unexported names, missing imports, build tags, and file organization issues.

The Error

You compile a Go program and get:

./main.go:10:5: undefined: myVariable

Or variations:

./main.go:15:2: undefined: MyFunction
./handler.go:8:12: undefined: Config
./main.go:5:2: undefined: fmt

The Go compiler cannot find the variable, function, type, or package you referenced. It either does not exist in the current scope, is in a different package, or is not exported.

Why This Happens

Go resolves names at compile time with strict rules:

  • Variables must be declared before use.
  • Functions and types must be defined in the same package or imported.
  • Names starting with lowercase are unexported (private to the package).
  • Names starting with uppercase are exported (accessible from other packages).
  • Each file must import the packages it uses.

Common causes:

  • Variable not declared. You used a name that was never declared with var, :=, or as a function parameter.
  • Wrong package scope. The name is defined in a different package but not imported or exported.
  • Unexported name. You are trying to use a lowercase name from another package.
  • Missing import. The package is not imported in the current file.
  • File not included in build. Build tags or wrong file naming excludes the file.
  • Typo. The name is misspelled.

Go’s name resolution model is deliberately minimal: there is no globals scope across packages, no implicit imports, no using statements, no implicit field access. Every name in a file is either a builtin (a tiny list: len, cap, make, new, panic, recover, nil, true, false, iota, plus the basic types), a name declared in the same package, or a qualified name from an imported package. If the compiler cannot find your name in one of those three buckets, you get undefined. There is no fourth bucket.

This strictness makes the error message accurate but unhelpful in one specific way: it does not tell you which bucket Go expected to find the name in. undefined: Config could mean “you forgot to declare Config,” “Config is in another file that did not get compiled,” “Config is unexported from another package,” or “you imported the wrong package and Config is in a different one.” All four produce the same error. Diagnosing is a process of elimination: same package? other package? local file? build-excluded file?

In Production: Incident Lens

By the time undefined: variable matters to an SRE, the build has failed. The question is not “how do I fix the code” — the developer can fix it. The question is why did this slip past CI and reach a deployment-time failure where it should never have arrived.

  • How it surfaces: As a red CI pipeline, a failed go build step in your build system, or — worst case — a failed image build during the deploy itself. Not as 5xx traffic. The error never reaches the runtime because Go is statically compiled; a missing name is a compile-time failure, not a runtime one. If you are seeing this at deploy time and not at PR-merge time, your CI is misconfigured.
  • Blast radius: Zero in production (nothing got deployed) if your deploy pipeline gates on a green build. Potentially total if the build error happens in a release-candidate build that you were about to ship and you have no rollback because the previous artifact already rolled off. The blast radius question is really about deploy queue lag: every minute the build is broken is a minute no fixes can go out, including the one for the unrelated incident in flight.
  • What catches it: The Go compiler is the catcher. The real question is whether the compiler ran on the right code at the right time. CI signals that should fire: go build ./... (not just go build . — the trailing dots matter), go vet ./..., staticcheck ./..., and a smoke test that builds the production image end-to-end. A green PR pipeline that did not run ./... is the most common gap.
  • Recovery sequence: Revert the offending commit on the main branch immediately so other developers can continue to merge. Then fix forward on a fix branch. Never let a broken-build state persist on main during business hours — every team member is now blocked from shipping.
  • Postmortem preventive: Three durable controls. First, ensure go build ./... runs on every PR before merge, with branch protection blocking merges on red. Second, pin the Go toolchain via go.mod’s toolchain directive so CI and local developer machines see the same compiler version (a method that exists in Go 1.22 but not 1.21 produces an “undefined” error if the build environment lags). Third, run a vendored-vs-module-cache check in CI: go mod verify plus a clean go build from a fresh module cache. This catches the case where a developer’s local cache contained a file that does not exist in the published module.

Fix 1: Declare the Variable

The simplest case. You used a variable that was never declared:

Broken:

func main() {
    fmt.Println(message)  // undefined: message
}

Fixed:

func main() {
    message := "Hello, World!"
    fmt.Println(message)
}

Or declare at package level:

var message = "Hello, World!"

func main() {
    fmt.Println(message)
}

Go does not have implicit variable creation. Every variable must be explicitly declared with var, :=, or as a function parameter.

Fix 2: Import the Package

If fmt, os, http, or any standard library name is undefined, the package is not imported:

Broken:

func main() {
    fmt.Println("Hello")  // undefined: fmt
}

Fixed:

import "fmt"

func main() {
    fmt.Println("Hello")
}

Multiple imports:

import (
    "fmt"
    "net/http"
    "os"
)

Most Go editors (VS Code with gopls, GoLand) auto-add imports when you save. If auto-import is not working, check your editor’s Go extension settings.

Pro Tip: Use goimports to automatically add and remove imports:

go install golang.org/x/tools/cmd/goimports@latest
goimports -w .

Configure your editor to run goimports on save. This eliminates all manual import management.

Fix 3: Export Names with Uppercase

In Go, lowercase names are private to the package. To use a name from another package, it must start with an uppercase letter:

Broken — unexported name:

// config/config.go
package config

var databaseURL = "postgres://localhost/mydb"  // lowercase — unexported
// main.go
package main

import "myapp/config"

func main() {
    fmt.Println(config.databaseURL)  // undefined: config.databaseURL
}

Fixed — export with uppercase:

// config/config.go
package config

var DatabaseURL = "postgres://localhost/mydb"  // Uppercase — exported
// main.go
fmt.Println(config.DatabaseURL)  // Works

This applies to variables, functions, types, constants, and struct fields. If a struct field is lowercase, other packages cannot access it directly.

For similar Go issues with unused variables, see Fix: Go declared and not used.

Fix 4: Fix File Organization in the Same Package

All .go files in the same directory must have the same package name. Functions and types defined in one file are visible in other files of the same package:

myapp/
  main.go      ← package main
  handlers.go  ← package main (same package!)
  utils.go     ← package main (same package!)

Broken — wrong package name:

// handlers.go
package handlers  // Wrong! Must be 'main' if in the same directory as main.go

func HandleRequest() {}

Fixed:

// handlers.go
package main

func HandleRequest() {}

Run all files together:

go run main.go handlers.go utils.go
# Or better:
go run .

If you run only go run main.go, Go does not compile handlers.go, and names defined there are undefined.

Common Mistake: Running go run main.go instead of go run . when your package has multiple files. go run main.go only compiles that single file. go run . compiles all .go files in the current directory with the same package name.

Fix 5: Fix Test File Issues

Names from _test.go files are not available in non-test files:

// helpers_test.go
package main

func testHelper() string {
    return "test"
}
// main.go
package main

func main() {
    testHelper()  // undefined: testHelper (only exists in test files)
}

Test files (_test.go) are compiled only during go test. Move shared helpers to a regular .go file if they are needed outside tests.

External test packages: Files with package main_test are in a different package and can only access exported names:

// math_test.go
package math_test  // External test package

import "myapp/math"

func TestAdd(t *testing.T) {
    result := math.Add(2, 3)  // Must use exported name
}

Fix 6: Fix Build Tags

Build tags can exclude files from compilation. A file with a build tag is only compiled when the tag matches:

//go:build linux

package main

func platformSpecific() {}

This file is only compiled on Linux. On macOS or Windows, platformSpecific is undefined.

Check your build tags:

go build -tags "linux" .

Common build tags:

  • //go:build linux — only on Linux
  • //go:build !windows — everything except Windows
  • //go:build integration — only when -tags integration is specified

If you need a function on all platforms, move it to a file without build tags or create platform-specific implementations:

utils_linux.go    ← //go:build linux
utils_darwin.go   ← //go:build darwin
utils_windows.go  ← //go:build windows

Fix 7: Fix Go Module Issues

If the undefined name is from an external package, the module might not be installed:

go mod tidy

This downloads missing dependencies and removes unused ones.

If the module is not found:

go get github.com/example/package@latest

For Go module resolution errors, see Fix: Go module not found. For “no required module provides package” errors, see Fix: Go no required module provides package.

Fix 8: Fix init() and Package Initialization

init() functions run automatically and cannot be called explicitly:

func init() {
    // Runs automatically when package is loaded
}

func main() {
    init()  // Cannot call init explicitly — but this is a different error
}

If you need initialization logic that can be called manually, use a regular function name like Initialize().

Package-level variables are initialized before init():

var config = loadConfig()  // Runs first

func init() {
    // config is available here
}

func main() {
    // Both config and init() have already run
}

Still Not Working?

If the error persists:

Check for generated code. Some Go projects use code generation (go generate, protobuf, sqlc, ent). Run the generator before building:

go generate ./...
go build .

Check for CGO dependencies. If the code uses import "C" (cgo), you need a C compiler installed. Without it, the cgo file is skipped and names defined there are undefined:

# Install gcc
sudo apt install gcc

# Or disable CGO if not needed
CGO_ENABLED=0 go build .

Check the Go version. Some features (generics, any type, min/max builtins) require a minimum Go version. Check your go.mod:

module myapp

go 1.22

Update if needed:

go install golang.org/dl/go1.22.0@latest

Check for interface embedding issues. If a type is supposed to implement an interface but methods are missing, the compiler might report “undefined” on the method call rather than a clear “does not implement” error.

Use your IDE. VS Code with gopls highlights undefined names immediately and offers “Quick Fix” suggestions. Install the Go extension and ensure gopls is running:

go install golang.org/x/tools/gopls@latest

Check GOFLAGS and build constraints in CI vs local. Set GOFLAGS=-mod=vendor locally but GOFLAGS=-mod=readonly in CI and you can get different build behavior. A file in vendor/ may exist locally and provide a name, but the CI build using the module cache resolves a different version of that package without the same symbol. Standardize GOFLAGS across environments.

Check the internal/ directory rule. Go enforces a path-based access rule: anything under internal/ is only importable by code rooted at the parent of internal/. If you move a package into internal/ and another module tried to import it, the import silently fails and every name from it shows as undefined. The error message points at the names, not at the import — easy to miss.

Check for case-only differences on macOS. macOS filesystems are case-insensitive by default, so Handler.go and handler.go are the same file. Linux build environments treat them as different. Code that compiles on a Mac because both filenames resolve to the same file fails on Linux CI with “undefined: HandleRequest” — the file with the function was not loaded because its name differs by case from what go build expects.

Check generated code freshness. Some toolchains (sqlc, ent, protoc-gen-go, wire) regenerate Go files from schema sources. If the schema changed but go generate ./... did not run before the build, the generated names your code expects do not exist yet. Run go generate ./... as a required CI step before go build.

If the Go compiler cannot find an entire package rather than a specific name (cannot find package), see Fix: Go cannot find package — that error has different root causes (GOPATH, module mode, vendoring) than the per-name undefined error.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles