Question

Go Equivalent of __dirname: Getting the Current File or Working Directory in Golang

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:

  1. The source file's directory

    • Where the .go file lives on disk.
    • This is what runtime.Caller can tell you.
    • Useful in limited cases, mostly debugging or special development-time tooling.
  2. 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.
  3. 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:

  • If you want files relative to where the program was launched, use os.Getwd().
  • If you want files next to the compiled binary, use os.Executable().
  • If you specifically need the source file path during development, runtime.Caller() works, but it is not the common general-purpose solution.

Important modern Go note

If you need to bundle static files with your Go program, the most idiomatic modern solution is often embed, not path calculation at runtime.

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.

Take Quiz

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("Executable directory:", exeDir)
	fmt.Println("File path:", filePath)
}

3. Get the source file's directory with runtime.Caller

Use this only if you truly need the source file location.

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)
	fmt.Println("Source file directory:", dir)
}

Why filepath instead of path?

  • path is mainly for slash-separated paths like URLs.
  • filepath is for filesystem paths and is the idiomatic choice for local files.

So this is better than:

path.Join(path.Dir(filename), "data.csv")

Use:

filepath.Join(filepath.Dir(filename), "data.csv")

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

  1. runtime.Caller(0) asks Go for information about the current function call.

    • 0 means “this function itself.”
    • 1 would mean the caller of this function.
  2. It returns several values:

    • program counter
    • filename
    • line number
    • success flag
  3. filename might be something like:

/home/user/project/main.go
  1. filepath.Dir(filename) extracts the directory:
/home/user/project
  1. filepath.Join(dir, "data.csv") creates:
/home/user/project/data.csv
  1. The program prints each value.

Why Caller(0) vs Caller(1)?

  • Caller(0) = this function
  • Caller(1) = the function that called this function

If you call runtime.Caller directly in main, 0 is usually what you want. If you wrap it inside a helper function and want the caller's file, then 1 may make sense.

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.json from the package directory

Using embed

Best when:

  • you want files compiled into the binary
  • you want zero runtime path problems
  • you are shipping templates, SQL files, JSON fixtures, or static assets

Example:

  • embedding data.csv directly into the executable
Take Quiz

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.

4. Helper functions with error handling

Developers usually wrap path logic in small helper functions instead of repeating it.

func executableDir() (string, error) {
	exe, err := os.Executable()
	if err != nil {
		return "", err
	}
	return filepath.Dir(exe), nil
}

5. Guard clauses

Go code often checks errors early and returns immediately:

path, err := configPath()
if err != nil {
	return err
}

content, err := os.ReadFile(path)
if err != nil {
	return err
}

This style is idiomatic and keeps code readable.

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(0)
wd, _ := os.Getwd()

Why it is a problem:

  • If the call fails, you may use empty or invalid paths.
  • Go encourages explicit error handling.

Better:

wd, err := os.Getwd()
if err != nil {
	return err
}

4. Confusing Caller(0) and Caller(1)

Broken expectation:

_, filename, _, _ := runtime.Caller(1)

If there is no caller above the current frame in the way you expect, you may get the wrong file.

Rule of thumb:

  • 0 for the current function
  • 1 for its caller

5. Assuming relative paths are based on the source file

Broken assumption:

os.ReadFile("data.csv")

This reads relative to the working directory, not the .go file location.

That surprises many beginners coming from other environments.

Comparisons

GoalRecommended APIIdiomatic?Notes
Get current working directoryos.Getwd()YesGood for CLI tools and relative paths from where the app starts
Get executable locationos.Executable()YesGood for deployed binaries with nearby assets
Get source file locationruntime.Caller()SometimesUseful for debugging, tests, or special tooling
Bundle file into binaryembedYes, modern GoBest when you want no runtime path dependency

path vs filepath

PackageUse forExample
pathslash-separated paths, often URL-likeweb paths
filepathoperating system file pathslocal files on Windows, Linux, macOS

Node.js __dirname vs Go approaches

EnvironmentFeatureMeaning
Node.js__dirnamedirectory of the current module file
Goruntime.Caller()source file location
Goos.Getwd()current working directory
Goos.Executable()compiled binary location
Take Quiz

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 .go source file location? Use runtime.Caller().
  • For local filesystem paths, prefer filepath over path.
  • 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 __dirname.

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 embed for 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 embed package if possible. It removes the need to locate files on disk at runtime.

Take Quiz

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

  1. Get the path of the currently running executable.
  2. Find the directory that contains the executable.
  3. Build the path to a config.json file in that directory.
  4. Read the file safely with proper error handling.
  5. Print the config contents to the terminal.
Take Quiz

Keep learning

Related questions

View more