ZetCode

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.

data/words.txt
sky
blue
rock
water

The file has four words, one per line, and ends with a newline.

main.go
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.

main.go
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.

data/langs.txt
Perl
Go
C#

The second file contains a short list of programming languages.

main.go
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.

main.go
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.

main.go
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.

main.go
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.

main.go
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.

main.go
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.