Go embed.FS Tutorial

| Categories Go  | Tags Go  embed 

πŸ“¦ Go embed.FS Tutorial

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