Go Embedding Series — 04. Advanced Usage of embed.FS File System

| Categories Go  | Tags Go  embed  embed.FS 

Go Embedding Series — 04. Advanced Usage of embed.FS File System

Go Embedding Series — 04. Advanced Usage of embed.FS File System

In the previous posts of this series, we explored how to embed files in Go using the //go:embed directive. But let’s pause and ask a natural next question:

“I’ve embedded some files. Now what? How can I access them as if they were real files?”

That’s where embed.FS comes in.


1. What Exactly Is embed.FS?

Before diving into code, let’s start with a thought experiment:

If you were to simulate a read-only file system inside your Go program, what capabilities would you need?

Likely, at minimum:

  • Read file contents
  • List directory contents
  • Open a file stream (e.g., for HTTP)

Now consider what embed.FS implements:

type FS interface {
    Open(name string) (fs.File, error)
}

Yes — embed.FS implements Go’s standard fs.FS interface, introduced in Go 1.16 to provide a unified abstraction over various file systems (disk, embedded, zip, etc.).

So we can say:

embed.FS is simply a read-only virtual file system that conforms to fs.FS.


2. The Three Essential Methods of embed.FS

2.1 ReadFile: Read Embedded File Content

data, err := fs.ReadFile(myFS, "static/config.json")
  • Returns the content as []byte
  • Errors often stem from path mismatches — always verify the path is relative to the .go file

Think of it as ioutil.ReadFile, but for embedded files.

2.2 ReadDir: List Files in an Embedded Directory

entries, err := fs.ReadDir(myFS, "templates")
for _, entry := range entries {
    fmt.Println(entry.Name(), entry.IsDir())
}

Perfect for:

  • Enumerating template files
  • Running batch SQL migrations or loading multiple configs

Analogy: Like opening a folder inside a zip file and seeing what’s inside.

2.3 Open: Stream Access to Embedded Files

f, err := myFS.Open("static/logo.png")
defer f.Close()
io.Copy(w, f) // Stream directly into HTTP response

Returns an fs.File, which supports methods like Read, Seek, and Stat. Use cases include:

  • Streaming large images/videos
  • Integration with HTTP servers
  • Preprocessing or caching resources

3. Real-World Use: Template System

In web servers, it’s common to embed HTML templates:

//go:embed templates/*
var tmplFS embed.FS

tmpl := template.Must(template.ParseFS(tmplFS, "templates/*.html"))

You can go further:

  • Hot-reload from disk in dev, use embed.FS in production
  • Handle multilingual pages: index.zh.html, index.en.html
  • Load partials on demand: combine ReadFile + Parse

Challenge: Try writing a LoadLocalizedTemplate(lang string) function.


4. Design Philosophy: Why Abstract fs.FS?

You might wonder:

“Why make all file systems — disk, zip, embedded — look the same?”

Because abstraction brings uniformity.

Imagine building a plugin system:

  • Plugin A is on disk
  • Plugin B is in a zip
  • Plugin C is embedded

If they all implement fs.FS, you can write one unified loader:

func LoadPlugin(fs fs.FS, path string) {
    data, _ := fs.ReadFile(fs, path)
    // ...
}

This is one of Go’s core philosophies:

Favor composition via interfaces over inheritance or complex structs.


5. Quick Q\&A

❓ Why can’t I read the embedded file?

  • Paths must be relative to the .go file that uses //go:embed
  • No variable paths or runtime composition
  • On Windows: case-sensitive, and always use /

❓ Why isn’t my updated file showing?

Embedded content is frozen at compile time. Rebuild your binary after any file change.

❓ How do I support multilingual templates?

Use ReadDir to scan the directory and filter by file suffix, e.g., index.zh.html, index.en.html.

❓ Can I write to embed.FS?

No. It’s strictly read-only and compiled into the binary.


6. Summary and Takeaways

  • embed.FS is a clean and powerful abstraction in Go
  • It works seamlessly with the standard fs.FS interface
  • It’s not just about embedding files — it teaches us how to design abstract interfaces

If you can use embed.FS as naturally as your local file system, then you truly understand it.


Further Reading & Challenges