//go:embed for Files and Templates
//go:embed tells the compiler to copy files into your package so they ship inside the binary.
Search across all documentation pages
//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.
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.
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:
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:
//go:embed variables can live in one package.assets/migrations/...).template.ParseFS loads templates without disk paths at runtime.//go:embed comments above package-level variables.embed.FS serves those bytes through io/fs APIs.init hook reads the filesystem on the target machine.| 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 |
//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.
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.
ReadFile uses the full embedded path including directories from the pattern. Fix: log fs.WalkDir once or match the pattern prefix exactly.os.Chdir in tests because data is not read from disk at runtime. Fix: test through the embedded FS APIs.ExecuteTemplate uses the template's defined name, not always the file name. Fix: call tpl.DefinedTemplates() during development.| 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 |
Yes, if they match the pattern rules.
Rare for production; useful for diagnostics or go version -m style tooling.
Embed reads from the source tree layout at compile time.
Vendored dependencies are separate modules; embed your own package files.
Yes.
Patterns follow path.Match rules; test with a small directory first.
Pick the variable type.
string avoids copying when you only read; []byte helps when you mutate a copy.
Yes, with limited meaning.
ModTime reflects embed metadata, not the original file timestamp on disk at runtime.
Each package needs its own //go:embed directive.
Duplication increases binary size if both packages link into the same binary.
Tools like goose and golang-migrate accept io/fs.FS.
Pass embed.FS or fs.Sub of your migrations directory.
Not automatically.
The linker stores bytes you provide; compress assets yourself if size matters.
go:embed is supported on many targets but verify your specific toolchain.
TinyGo has size constraints; keep embedded assets small.
Yes.
Put different //go:embed variables in files with opposing //go:build lines.
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).
Reviewed by Chris St. John·Last updated Jul 16, 2026