You golang create nested directory trees either level by level with os.Mkdir, or in one step with os.MkdirAll, which creates parent components as needed and succeeds if the path already exists. Combine with filepath.Join for portable paths. Shell-style mkdir -p is available through os/exec when that is clearer for your deployment.
Tested with Go 1.24 on Linux.
os.Mkdir one level at a time
Each call creates a single directory; the parent must exist, and repeating the program fails if the directory is already there unless you check first.
package main
import (
"fmt"
"os"
)
func main() {
myDir := "/tmp/glc-topdir"
if err := os.Mkdir(myDir, 0o755); err != nil && !os.IsExist(err) {
panic(err)
}
if err := os.Mkdir(myDir+"/subdir1", 0o755); err != nil && !os.IsExist(err) {
panic(err)
}
if err := os.Mkdir(myDir+"/subdir2", 0o755); err != nil && !os.IsExist(err) {
panic(err)
}
fmt.Println("ok")
}Use os.IsExist or errors.Is(err, fs.ErrExist) when you intentionally allow reruns.
os.MkdirAll (recommended)
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
p := filepath.Join("/tmp", "glc-mkdirall", "a", "b", "c")
if err := os.MkdirAll(p, 0o755); err != nil {
panic(err)
}
fi, err := os.Stat(p)
if err != nil {
panic(err)
}
fmt.Println(fi.IsDir())
}You should see true.
Helper that skips if the leaf already exists
package main
import (
"fmt"
"os"
"path/filepath"
)
func ensureDir(path string) error {
_, err := os.Stat(path)
if err == nil {
fmt.Println("already exists:", path)
return nil
}
if !os.IsNotExist(err) {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.Mkdir(path, 0o755)
}
func main() {
path := filepath.Join("/tmp", "glc-ensure", "subdir1", "subdir2")
if err := ensureDir(path); err != nil {
panic(err)
}
fmt.Println("created or skipped:", path)
}You should see a created or skipped message and the final path printed.
Shell: mkdir -p via os/exec
package main
import (
"fmt"
"os/exec"
)
func main() {
path := "/tmp/glc-exec/a/b/c"
cmd := exec.Command("mkdir", "-p", path)
if err := cmd.Run(); err != nil {
panic(err)
}
fmt.Println("ok")
}Prefer os.MkdirAll when you do not need shell features; use exec when mirroring scripts or invoking platform tools (exec in Go).
Summary
For golang create nested directory workflows, os.MkdirAll with filepath.Join is the standard library sweet spot: idempotent for existing paths, one call for the whole tree, and no shell dependency. Use layered Mkdir plus Stat only when you need stricter control, and exec.Command("mkdir","-p",path) when shell parity matters.
References
Frequently Asked Questions
What is the usual way to create nested directories in Go?
Prefer os.MkdirAll(path, perm) so parents are created and existing directories are not an error.
When should I use os.Mkdir instead of MkdirAll?
Use Mkdir for a single new directory when the parent must already exist; repeat for each level or combine with Stat if you are mirroring legacy shell scripts.
How do I build nested paths portably?
Use filepath.Join rather than hard-coded slashes, especially when targeting Windows as well as Linux.
How can I run mkdir -p from Go?
Use os/exec with mkdir and -p; validate paths and remember this shells out to the platform mkdir binary instead of using only the standard library.
What permission bits should I pass?
Use os.FileMode literals such as 0o755 for common directory permissions; avoid world-writable modes unless you intend them.

