Question
In Node.js, I can use __dirname to get the directory of the current file. What is the equivalent of this in Go?
I found an article that uses runtime.Caller like this:
_, filename, _, _ := runtime.Caller(1)
f, err := os.Open(path.Join(path.Dir(filename), "data.csv"))
Is this the correct or idiomatic way to do it in Go, especially if I want to open a file located next to the source file?
Short Answer
By the end of this page, you will understand the difference between a source file location, the current working directory, and the executable location in Go. You will also learn when runtime.Caller is appropriate, why it is usually not the idiomatic replacement for Node.js __dirname, and which Go APIs are better depending on what you are actually trying to do.
Concept
In Go, there is no exact built-in equivalent to Node.js __dirname.
That is because Go programs are usually compiled into binaries, and once compiled, the source file path is often not the thing you want at runtime.
The three different paths people often confuse
When developers ask for the “current file directory,” they usually mean one of these:
-
The source file's directory
- Where the
.gofile lives on disk. - This is what
runtime.Callercan tell you. - Useful in limited cases, mostly debugging or special development-time tooling.
- Where the
-
The current working directory
- The directory from which the program was started.
- Retrieved with
os.Getwd(). - Common for reading config files, relative paths, and CLI tools.
-
The executable's directory
- Where the compiled binary is located.
- Retrieved with
os.Executable(). - Useful when shipping an app with files next to the binary.
Why this matters
In Node.js, __dirname is tied to the module file being executed. In Go, runtime code is typically not written around source-file-relative paths, because the binary may run on another machine where the original source files do not exist.
So the idiomatic Go answer depends on your goal:
Mental Model
Think of a Go program like a traveler with three different addresses:
- Home address = source file directory
- Current location = working directory
- Hotel address = executable directory
If you ask, “Where am I?” the answer depends on what you really mean.
runtime.Caller() tells you the home address of the source code.
os.Getwd() tells you the current location.
os.Executable() tells you the hotel address where the binary is staying.
Most runtime tasks care about the current location or the hotel address, not the original source file.
Syntax and Examples
1. Get the current working directory
Use this when your program should read files relative to where it was started.
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
wd, err := os.Getwd()
if err != nil {
panic(err)
}
filePath := filepath.Join(wd, "data.csv")
fmt.Println("Working directory:", wd)
fmt.Println("File path:", filePath)
}
os.Getwd() returns the current working directory.
2. Get the executable's directory
Use this when your program ships with files next to the binary.
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
exePath, err := os.Executable()
if err != nil {
panic(err)
}
exeDir := filepath.Dir(exePath)
filePath := filepath.Join(exeDir, "data.csv")
fmt.Println("Executable path:", exePath)
fmt.Println(, exeDir)
fmt.Println(, filePath)
}
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func main() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("could not get caller info")
}
dir := filepath.Dir(filename)
file := filepath.Join(dir, "data.csv")
fmt.Println("filename:", filename)
fmt.Println("dir:", dir)
fmt.Println("file:", file)
}
What happens step by step
-
runtime.Caller(0)asks Go for information about the current function call.0means “this function itself.”1would mean the caller of this function.
-
It returns several values:
- program counter
- filename
- line number
- success flag
-
filenamemight be something like:
/home/user/project/main.go
Real World Use Cases
Using os.Getwd()
Common in:
- CLI tools that read files from the current folder
- scripts run from a project directory
- local development commands like
go run .
Example:
- A CSV importer that expects
./data.csv - A config loader that reads
./config.json
Using os.Executable()
Common in:
- desktop apps
- self-contained command-line binaries
- deployed services that keep templates or assets near the binary
Example:
- A binary in
/opt/myapp/that loads/opt/myapp/config.yaml
Using runtime.Caller()
Common in:
- debugging and logging utilities
- test helpers
- development tools that need source file metadata
- locating test fixtures relative to test source files
Example:
- A test file loading
testdata/sample.jsonfrom the package directory
Using
Real Codebase Usage
In real Go codebases, developers usually avoid hard-coding logic around source file paths for application runtime.
Common patterns
1. Working-directory based loading
func loadConfig() ([]byte, error) {
return os.ReadFile("config.json")
}
This is simple and common for command-line tools.
2. Executable-relative loading
func configPath() (string, error) {
exe, err := os.Executable()
if err != nil {
return "", err
}
return filepath.Join(filepath.Dir(exe), "config.json"), nil
}
This is common in packaged applications.
3. Embedded assets
package main
import _ "embed"
//go:embed data.csv
var csvData []byte
This avoids filesystem path issues entirely.
Common Mistakes
1. Using runtime.Caller when you really want the working directory
Broken idea:
_, filename, _, _ := runtime.Caller(0)
filePath := filepath.Join(filepath.Dir(filename), "config.json")
Why this can be wrong:
- It ties runtime behavior to source code layout.
- The source files may not exist where the binary runs.
Use os.Getwd() or os.Executable() depending on intent.
2. Using path instead of filepath
Broken or non-idiomatic:
import "path"
Better:
import "path/filepath"
Use filepath for local filesystem paths.
3. Ignoring errors
Broken code:
_, filename, _, _ := runtime.Caller()
wd, _ := os.Getwd()
Comparisons
| Goal | Recommended API | Idiomatic? | Notes |
|---|---|---|---|
| Get current working directory | os.Getwd() | Yes | Good for CLI tools and relative paths from where the app starts |
| Get executable location | os.Executable() | Yes | Good for deployed binaries with nearby assets |
| Get source file location | runtime.Caller() | Sometimes | Useful for debugging, tests, or special tooling |
| Bundle file into binary | embed | Yes, modern Go | Best when you want no runtime path dependency |
path vs filepath
Cheat Sheet
Quick reference
Current working directory
wd, err := os.Getwd()
Executable path
exe, err := os.Executable()
dir := filepath.Dir(exe)
Source file path
_, filename, _, ok := runtime.Caller(0)
dir := filepath.Dir(filename)
Join file paths
filepath.Join(dir, "data.csv")
Rules of thumb
- Want paths relative to where the command runs? Use
os.Getwd(). - Want paths relative to the binary? Use
os.Executable(). - Want the
.gosource file location? Useruntime.Caller(). - For local filesystem paths, prefer
filepathoverpath. - Always check errors.
- If the file should ship inside the program, consider
embed.
Edge case reminder
runtime.Caller() depends on source file information and is not the general replacement for Node.js .
FAQ
Is runtime.Caller() the Go equivalent of __dirname?
Not exactly. It can give you the source file path, which is similar in spirit, but it is not usually the main runtime solution in Go.
What is the idiomatic way to open a file in Go?
It depends on where the file is expected to be:
- use
os.Getwd()for working-directory-relative files - use
os.Executable()for executable-relative files - use
embedfor bundled files
Should I use path.Join or filepath.Join in Go?
Use filepath.Join for filesystem paths. path.Join is mainly for slash-separated paths such as URLs.
Why does os.ReadFile("data.csv") sometimes fail?
Because relative paths are resolved from the current working directory, which may not be the same as your source file directory.
When is runtime.Caller() a good choice?
It is useful for debugging, logging, tests, and tools that need source code location information.
What if I want to include a CSV or template file inside the program?
Use the package if possible. It removes the need to locate files on disk at runtime.
Mini Project
Description
Build a small Go program that loads an application config file from the same directory as the compiled executable. This demonstrates a common real-world pattern for command-line tools and deployable binaries, and helps you see why executable-relative paths are usually more practical than source-file-relative paths.
Goal
Create a program that finds config.json next to the executable, reads it, and prints its contents.
Requirements
- Get the path of the currently running executable.
- Find the directory that contains the executable.
- Build the path to a
config.jsonfile in that directory. - Read the file safely with proper error handling.
- Print the config contents to the terminal.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.