Go Embedding Series — 03. The Ultimate Guide to go:embed Syntax and Usage

| Categories Go  | Tags Go  embed  Go Embedding Series 

# The Ultimate Guide to `go:embed` Syntax and Usage

> Series: Go Embedding Features — The Ultimate Guide to `go:embed` Syntax and Usage

Since the release of Go 1.16, the `embed` package has become part of the standard library, allowing developers to embed static resources directly into the compiled binary. Whether you're building a web application, CLI tool, or want to avoid external file dependencies during deployment, `go:embed` is a powerful asset that should not be overlooked.

---

## 1. Detailed Syntax Overview

### 1.1 Importing the `embed` Package

```go
import "embed"

You must explicitly import the embed package, even if you only use its annotations.

1.2 Three Supported Variable Types

Type Description
string Reads file content as UTF-8 encoded text
[]byte Raw binary file data
embed.FS Virtual read-only file system for multiple files or directories

1.3 Example: Embedding a Single File

import _ "embed"

//go:embed hello.txt
var helloText string
fmt.Println(helloText)

Text is embedded as UTF-8. You can also embed binary files:

//go:embed logo.png
var logo []byte

1.4 Example: Embedding Multiple Files (Wildcard)

//go:embed static/*.js static/*.css
var assets embed.FS

Supports glob patterns (wildcards), but not recursive ones like **. The glob matches paths, not file content.

1.5 Example: Embedding a Directory

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

This is ideal for web projects or template engines.


2. Practical Use Cases

2.1 Build a Self-Contained Web App

//go:embed static/*
var staticFiles embed.FS

func main() {
	fs := http.FS(staticFiles)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(fs)))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

No need to deploy the static/ directory separately—the app binary includes it all.


2.2 Embed and Render HTML Templates

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

func renderTemplate(w http.ResponseWriter) {
	t := template.Must(template.ParseFS(tmplFS, "templates/index.html"))
	t.Execute(w, map[string]string{"Title": "Welcome!"})
}

Perfect for use with html/template or text/template.


2.3 Embed Default Config Files

//go:embed config.yaml
var configYAML []byte

func loadDefaultConfig() Config {
	var c Config
	_ = yaml.Unmarshal(configYAML, &c)
	return c
}

Great for CLI apps that need a default configuration, which users can override via --config.


2.4 Embed SQL or Migration Scripts

//go:embed migrations/*.sql
var migrationFS embed.FS

func runMigrations(db *sql.DB) {
	files, _ := fs.ReadDir(migrationFS, "migrations")
	for _, f := range files {
		data, _ := migrationFS.ReadFile("migrations/" + f.Name())
		db.Exec(string(data))
	}
}

Perfect for tools like gorm, sqlc, or golang-migrate.


3. Working with embed.FS

3.1 ReadFile

data, err := fs.ReadFile(embedFS, "templates/index.html")

Returns the file as []byte, suitable for parsers or direct output.


3.2 ReadDir

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

Similar to os.ReadDir, but operates on the embedded filesystem.


3.3 Open File Handle

f, err := embedFS.Open("static/logo.png")
defer f.Close()
io.Copy(w, f) // Directly write to HTTP response

Note: embed.FS is read-only—you cannot modify embedded files.


4. Limitations and Caveats

Limitation Description
Compile-Time Embedding Only Files must exist at build time; runtime changes are not supported
No Recursive Globs (**) You must list each subdirectory explicitly or use specific patterns (e.g. *.ext)
Relative Paths Paths are relative to the Go file, not the project root
Read-Only Access Embedded files are immutable and cannot be written to
No Dynamic Paths //go:embed paths must be string constants, not variables or expressions

5. Comparison with Third-Party Tools

Feature go:embed rice statik
Built-in ✅ Yes ❌ No ❌ No
Supports Directory ✅ Yes ✅ Yes ✅ Yes
Hot Reload Support ❌ No ✅ Yes (dev mode) ❌ No
Build Performance ✅ Fast ❌ Slower (requires code generation) ❌ Slower
Ease of Use ✅ Simple ❌ Complex ❌ Moderate

Recommendation: use go:embed for all small to medium projects; only consider rice if hot reloading is essential.


6. Debugging Tips & Common Pitfalls

6.1 Embedded File Not Found?

  • Ensure the go:embed comment is placed directly above the variable.
  • Confirm the path is relative to the .go file containing the directive.
  • File names and extensions are case-sensitive.

6.2 File Not Updating?

Embedded resources are locked at build time. Rebuild the binary after changing embedded files.

6.3 UTF-8 Issues with Chinese or Non-ASCII Characters?

Make sure the embedded files are saved as UTF-8. If reading []byte, manually convert:

str := string(data) // assuming UTF-8 encoding

7. Best Practices Summary

✅ Use embed.FS for multi-file or directory-based resource management ✅ Organize static resources into folders like static/, templates/, or assets/ ✅ Prefer go:embed over external dependencies or Docker bind mounts ✅ Combine with go generate or checksum checks for enhanced build workflows


Example Projects


Conclusion

go:embed represents a significant step toward standardized, modular resource management in Go. It greatly simplifies deployment for CLI apps, web servers, and containerized applications. If you’re still manually copying static files during deployment—now’s the time to embrace go:embed.