Go embed.FS Tutorial

| Categories Go  | Tags Go  embed 

Introduced in Go 1.16, embed.FS revolutionizes how static assets are managed. This tutorial covers everything from the basics to advanced use cases, with practical examples and solutions.


✨ Why Use embed.FS?

In web projects, CLI tools, config systems, or template engines, you often need to load static files like:

  • HTML / CSS / JS pages
  • YAML / JSON config files
  • SQL initialization scripts
  • Text templates or Markdown files
  • Image assets (PNG, SVG, etc.)

Previously, tools like go-bindata or vfsgen were commonly used to embed files into Go binaries. However, they:

  • Require extra build steps
  • Are harder to debug and maintain
  • Introduce non-standard build flows

With Go 1.16, the official embed package offers built-in, safe, cross-platform, and dependency-free asset embedding.


📘 Basic Usage

1️⃣ Embed a Single File

import "embed"

//go:embed hello.txt
var f embed.FS

func main() {
	data, _ := f.ReadFile("hello.txt")
	fmt.Println(string(data))
}

Just add a //go:embed directive — no code generation or external tools needed.


2️⃣ Embed Entire Directories or Multiple Files

//go:embed templates/*.tmpl static/*
var assets embed.FS

data, _ := assets.ReadFile("templates/header.tmpl")
  • Supports wildcards (*, **)
  • Use relative paths consistent with your project layout

3️⃣ Use with html/template

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

func main() {
	tmpl := template.Must(template.ParseFS(tmplFS, "templates/index.tmpl"))
	tmpl.Execute(os.Stdout, map[string]string{"Title": "Hello, embed"})
}

💡 Advanced Patterns & Real-World Usage

1️⃣ Serve Static Assets Over HTTP

//go:embed static
var staticFS embed.FS

func main() {
	subFS, _ := fs.Sub(staticFS, "static")
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(subFS))))
	http.ListenAndServe(":8080", nil)
}

Use fs.Sub() to set a virtual root for static resources — perfect for embedded frontend files.


2️⃣ Create a Resource Manager Wrapper

type AssetManager struct {
	fs fs.FS
}

func NewAssetManager(f fs.FS) *AssetManager {
	return &AssetManager{fs: f}
}

func (am *AssetManager) MustRead(path string) string {
	data, err := am.fs.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return string(data)
}

// Usage
//go:embed config/*
var configFS embed.FS
var Config = NewAssetManager(configFS)

fmt.Println(Config.MustRead("config/app.yaml"))

3️⃣ Support Hot Reloading in Dev Mode

func readFile(devPath string, embedFS fs.FS, path string) ([]byte, error) {
	if _, err := os.Stat(devPath); err == nil {
		return os.ReadFile(devPath)
	}
	return embedFS.ReadFile(path)
}

Load from disk during development, and fall back to embed.FS in production — supporting both hot reloads and packaging.


4️⃣ Embed Gzipped Resources for Better Performance

//go:embed static/*.gz
var gzFS embed.FS

func serveGzip(w http.ResponseWriter, r *http.Request) {
	data, _ := gzFS.ReadFile("static/app.js.gz")
	w.Header().Set("Content-Encoding", "gzip")
	w.Header().Set("Content-Type", "application/javascript")
	w.Write(data)
}

Pre-compress assets and serve them directly — ideal for high-performance web responses.


5️⃣ Use with VFS Libraries (e.g., Afero)

import "github.com/spf13/afero"

func FSAdapter(fsys fs.FS) afero.Fs {
	return afero.NewReadOnlyFs(afero.NewIOFS(fsys))
}

Wrap embed.FS to use with virtual file system libraries — useful for plugin systems or cross-environment compatibility.


📁 Project Layout Example

myapp/
├── main.go
├── static/
│   └── app.js
├── templates/
│   └── index.tmpl
├── config/
│   └── app.yaml

In main.go:

//go:embed static templates config
var appFS embed.FS

Then use:

tmpl := template.Must(template.ParseFS(appFS, "templates/*.tmpl"))
tmpl.ExecuteTemplate(w, "index.tmpl", data)

🔐 Caveats & Limitations

Limitation Details
Only supports relative paths ../ is not allowed
Files must exist at compile time Runtime-generated files can’t be embedded
Empty directories are ignored Embed skips folders with no files
Must be declared at package level Cannot declare embed variables inside functions

✅ When Should You Use embed.FS?

Scenario Recommendation
CLI tools with built-in templates/config ✅ Highly recommended
Embedding web assets in Go servers ✅ Recommended
Scaffolding generators with templates ✅ Recommended
Live reload in dev environment ⚠️ Needs fallback or dev-mode toggle
Embedding large media files (>10MB) ❌ Not recommended (use CDN/storage)

📚 References