//go:embed for Files and Templates
//go:embed tells the compiler to copy files into your package so they ship inside the binary.
You read them through string, []byte, or embed.FS without opening paths on the deployment host.
Summary
go:embed landed in Go 1.16 and replaced most bindata-style code generators for static assets.
The directive applies to variables in the same package, using glob patterns relative to the source file.
embed.FS implements io/fs.FS, so http.FileServer, fs.WalkDir, and template.ParseFS compose naturally.
Embedded data is read-only; mutate copies in memory if you need to change content at runtime.
Recipe
Quick-reference recipe card - copy-paste ready.
package assets
import "embed"
//go:embed migrations/*.sql
var Migrations embed.FS
//go:embed config/default.yaml
var DefaultConfig []byteWhen to reach for this:
- Shipping SQL migrations or default YAML/JSON with a CLI or service.
- Bundling HTML templates and static UI for a single-binary deployment.
- Guaranteeing assets exist in tests without fixture path gymnastics.
- Building tools that must run offline without a config directory.
Working Example
example.com/app/
main.go
assets/
migrations/
001_init.sql
templates/
home.html
// main.go
package main
import (
"embed"
"html/template"
"io/fs"
"log"
"net/http"
)
//go:embed assets/migrations/*.sql
var migrations embed.FS
//go:embed assets/templates/*.html
var templates embed.FS
func main() {
sql, err := fs.ReadFile(migrations, "assets/migrations/001_init.sql")
if err != nil {
log.Fatal(err)
}
log.Printf("migration bytes: %d", len(sql))
tpl, err := template.ParseFS(templates, "assets/templates/*.html")
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_ = tpl.ExecuteTemplate(w, "home.html", nil)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}What this demonstrates:
- Multiple
//go:embedvariables can live in one package. - FS paths retain the directory prefix from the pattern (
assets/migrations/...). template.ParseFSloads templates without disk paths at runtime.
Deep Dive
How It Works
- The compiler scans
//go:embedcomments above package-level variables. - Matching files are read from the module source tree during compilation.
- Bytes are stored in the compiled package metadata and linked into the final binary.
- At runtime,
embed.FSserves those bytes throughio/fsAPIs. - No
inithook reads the filesystem on the target machine.
Pattern Rules
| Rule | Detail |
|---|---|
| Variable types | string, []byte, embed.FS only |
| Location | Same package directory or subdirectory |
| Forbidden | .., absolute paths, symlinks outside tree |
| Dotfiles | Names starting with . are excluded unless named explicitly |
| Empty match | Compile error if a pattern matches nothing |
Serving Static Files
//go:embed static/*
var static embed.FS
sub, _ := fs.Sub(static, "static")
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))fs.Sub drops the embedded prefix so URLs map cleanly.
Go Notes
import _ "embed" // required when using //go:embed without referencing embed in codeIf you only embed into []byte and never name embed.FS, the blank import satisfies the compiler.
Gotchas
- Wrong FS path -
ReadFileuses the full embedded path including directories from the pattern. Fix: logfs.WalkDironce or match the pattern prefix exactly. - Expecting hot reload - embedded content is fixed at build time. Fix: use build tags to swap embed vs disk in dev.
- Large binaries - embedding multi-megabyte assets bloats every deploy. Fix: host big media externally; embed only what must be local.
- Secrets in embed - files on the build machine get baked in. Fix: never embed production secrets; inject at runtime.
- Tests and working directory - embed ignores
os.Chdirin tests because data is not read from disk at runtime. Fix: test through the embedded FS APIs. - Template name mismatches -
ExecuteTemplateuses the template's defined name, not always the file name. Fix: calltpl.DefinedTemplates()during development.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Disk files + ConfigMap/volume | Large or frequently updated assets | You need strict single-binary portability |
go:generate bindata tools | Legacy projects already on generators | Starting greenfield Go 1.16+ code |
| Embed only defaults; fetch rest | Hybrid offline/online | Every byte must be air-gapped |
text/template with ParseFiles in dev | Fast template iteration locally | Production must not depend on template paths |
FAQs
Can I embed go.mod or source files?
Yes, if they match the pattern rules.
Rare for production; useful for diagnostics or go version -m style tooling.
Does embed work with vendor/?
Embed reads from the source tree layout at compile time.
Vendored dependencies are separate modules; embed your own package files.
Can I use ** in patterns?
Yes.
Patterns follow path.Match rules; test with a small directory first.
How do I embed one file as string vs []byte?
Pick the variable type.
string avoids copying when you only read; []byte helps when you mutate a copy.
Does embed.FS support ModTime?
Yes, with limited meaning.
ModTime reflects embed metadata, not the original file timestamp on disk at runtime.
Can multiple packages embed the same folder?
Each package needs its own //go:embed directive.
Duplication increases binary size if both packages link into the same binary.
How do migrations frameworks use embed?
Tools like goose and golang-migrate accept io/fs.FS.
Pass embed.FS or fs.Sub of your migrations directory.
Is embedded data compressed?
Not automatically.
The linker stores bytes you provide; compress assets yourself if size matters.
What about WASM and TinyGo?
go:embed is supported on many targets but verify your specific toolchain.
TinyGo has size constraints; keep embedded assets small.
Can I conditionally embed with build tags?
Yes.
Put different //go:embed variables in files with opposing //go:build lines.
Related
- Beyond the Spec: Embed, Build Modes, and Assembly - conceptual overview
- Advanced Language Features Basics - intro embed examples
- Build Constraints & Platform-Specific Files - per-platform embed sets
- Packages & Modules Basics - module layout for assets
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).