go / embed

I use //go:embed to bundle files into a Go binary at compile time.

Single file

Embed a single file as a byte slice:

import _ "embed"

//go:embed schema.sql
var schema []byte

func initDB(db *sql.DB) error {
    _, err := db.Exec(string(schema))
    return err
}

Or as a string:

//go:embed version.txt
var version string

Multiple files

Embed multiple files into an embed.FS:

import "embed"

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

func loadTemplate(name string) ([]byte, error) {
    return templatesFS.ReadFile("templates/" + name)
}

The path in ReadFile must match the embedded path exactly, including the directory prefix.

Patterns

Patterns work like filepath.Glob:

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

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

//go:embed templates/*.html templates/*.css
var assetsFS embed.FS

Multiple directives can target the same variable:

//go:embed index.json
//go:embed *.gotmpl
//go:embed *.json
var templatesFS embed.FS

HTTP file server

Serve embedded files over HTTP:

import (
    "embed"
    "io/fs"
    "net/http"
)

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

func main() {
    // Strip "static/" prefix so /app.css serves static/app.css
    stripped, _ := fs.Sub(staticFS, "static")
    http.Handle("/", http.FileServer(http.FS(stripped)))
    http.ListenAndServe(":8080", nil)
}

Templates

Parse a layout shell and one page template into a single template set. The page defines {{define "content"}}...{{end}}; the layout renders it via {{template "content" .}}:

//go:embed ui/*.html
var uiFS embed.FS

func parseTemplates(page string) (*template.Template, error) {
    return template.New("").ParseFS(uiFS, "ui/index.html", "ui/"+page)
}

One shared chrome (header, footer, scripts), many pages, no duplication.

Migrations

Bundle SQL migrations with the binary so production deploys carry their own DDL:

const MigrateDir = "db/migrate"

//go:embed db/migrate/*.sql
var MigrateFS embed.FS

Load and run them via io/fs:

entries, _ := fs.ReadDir(MigrateFS, MigrateDir)
for _, e := range entries {
    sql, _ := fs.ReadFile(MigrateFS, MigrateDir+"/"+e.Name())
    db.Exec(string(sql))
}

No separate migration directory to ship, no path-finding code.

When to use

When not to use

See fingerprint for cache-busting embedded assets in production web apps.

← All articles