Go embed
last modified October 30, 2026
In this article we show how to embed files in a Go program. The
embed package, introduced in Go 1.16, stores the contents of
files in the binary at compile time, so the program no longer needs the files
at run time.
The examples were compiled and run with Go 1.27.0. They use a small project with two text files, two HTML pages and a PNG image.
Embedding a text file into bytes
A //go:embed directive above a package-level variable of type
[]byte or string stores the file in the variable. The
program must import the embed package, even when it does not use
any of its identifiers.
sky blue rock water
The file has four words, one per line, and ends with a newline.
package main
import (
_ "embed"
"fmt"
)
//go:embed data/words.txt
var data []byte
func main() {
fmt.Printf("%q\n", string(data))
fmt.Println("bytes:", len(data))
}
The blank import registers the package without referring to it by name. The quoted output shows that the file contents, including the final newline, were copied exactly.
$ go run main.go "sky\nblue\nrock\nwater\n" bytes: 20
Embedding into a string
A string variable receives the same bytes as text, which is convenient for templates and other textual resources.
package main
import (
_ "embed"
"fmt"
"strings"
)
//go:embed data/words.txt
var words string
func main() {
lines := strings.Split(strings.TrimSpace(words), "\n")
fmt.Println(len(lines), lines[0], lines[len(lines)-1])
fmt.Println(strings.ToUpper(words))
}
TrimSpace removes the trailing newline before splitting, so the
slice has four elements instead of five. The upper-case output still contains
the newline at the end of the file.
$ go run main.go 4 sky water SKY BLUE ROCK WATER
Embedding several files
An embed.FS holds a tree of files. The directive accepts a list of
patterns, and the files are read with the usual fs functions.
Perl Go C#
The second file contains a short list of programming languages.
package main
import (
"embed"
"fmt"
)
//go:embed data/words.txt data/langs.txt
var files embed.FS
func main() {
words, err := files.ReadFile("data/words.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Print(string(words))
langs, err := files.ReadFile("data/langs.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Print(string(langs))
_, err = files.ReadFile("data/missing.txt")
fmt.Println(err)
}
The paths inside the embedded file system are the paths used in the directive.
Reading a file that was not embedded returns
fs.ErrNotExist wrapped in a *fs.PathError.
$ go run main.go sky blue rock water Perl Go C# open data/missing.txt: file does not exist
Walking the embedded tree
embed.FS implements fs.FS, so fs.WalkDir
and the other file system helpers work without knowing that the files are
compiled into the binary.
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed data/* public/*
var content embed.FS
func main() {
err := fs.WalkDir(content, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
fmt.Printf("%-22s %6d\n", path, info.Size())
return nil
})
if err != nil {
fmt.Println(err)
}
}
Only files are stored; directories are synthesized by the file system
implementation and have size zero. The walk starts at . and visits
the entries in lexical order.
$ go run main.go . 0 data 0 data/langs.txt 11 data/words.txt 20 public 0 public/about.html 128 public/index.html 126
File metadata
fs.Stat and fs.ReadDir work on an embedded file
system as well. Embedded files are read-only, and their modification time is
the zero time because no timestamp is stored in the binary.
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed data
var content embed.FS
func main() {
info, err := fs.Stat(content, "data/words.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(info.Name(), info.Size(), info.Mode())
fmt.Println("zero time:", info.ModTime().IsZero())
entries, err := fs.ReadDir(content, "data")
if err != nil {
fmt.Println(err)
return
}
for _, e := range entries {
fmt.Println(e.Name())
}
}
The file mode is -r--r--r-- for every embedded file. The
directory listing is sorted by name, and the modification time check returns
true.
$ go run main.go words.txt 20 -r--r--r-- zero time: true langs.txt words.txt
Embedding binary data
The directive is not limited to text. A PNG image can be embedded and decoded
from memory with the image package.
package main
import (
"bytes"
_ "embed"
"fmt"
"image"
_ "image/png"
)
//go:embed assets/logo.png
var logo []byte
func main() {
img, format, err := image.Decode(bytes.NewReader(logo))
if err != nil {
fmt.Println(err)
return
}
fmt.Println("format:", format)
fmt.Println("bounds:", img.Bounds())
fmt.Println("bytes: ", len(logo))
}
The PNG decoder is registered with a blank import. The embedded byte slice is the complete file, so the image can be decoded without any access to the original directory.
$ go run main.go format: png bounds: (0,0)-(120,80) bytes: 219
Serving embedded files
A common use of embed is a web server that ships its static files
inside the executable. fs.Sub exposes the
public directory as the root of a file system, and
http.FileServer serves it.
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed public
var content embed.FS
func main() {
html, err := fs.Sub(content, "public")
if err != nil {
log.Fatal(err)
}
http.Handle("/", http.FileServer(http.FS(html)))
log.Println("listening on http://localhost:8098")
log.Fatal(http.ListenAndServe(":8098", nil))
}
The embedded directory public contains index.html and
about.html. The file server serves the index at the root, the
about page by name, and a 404 for a path that does not exist.
$ go run main.go &
2026/10/30 20:32:46 listening on http://localhost:8098
$ curl -i localhost:8098/
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 126
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
...
$ curl -o /dev/null -w '%{http_code}\n' localhost:8098/missing.html
404
Directive rules
The directive must be placed immediately before a package-level variable, and the pattern must match at least one file. A pattern that matches nothing stops the build with an error.
package main
import _ "embed"
//go:embed data/nope.txt
var data []byte
func main() {
_ = data
}
The pattern cannot contain .. or name an absolute path, so a
program cannot embed files outside its module. A file whose name starts with a
dot or an underscore is skipped unless the pattern names it explicitly. The
embed import is also required: without it the compiler reports
that the directive needs import "embed".
$ go build ./main.go:5:12: pattern data/nope.txt: no matching files found
Source
Go embed package - reference
Go blog - embed
In this article we have embedded files in Go. We stored text in byte slices and
strings, embedded several files in an embed.FS, walked and
inspected the embedded tree, decoded a PNG from memory, served static files
from the binary, and reviewed the rules of the directive.
Author
List all Go tutorials.