Generating UUIDs in Go
On this page
Go has no UUID type in the standard library. The community converged on github.com/google/uuid as the de facto standard — small, correct, well-maintained.
Quick start with google/uuid
go get github.com/google/uuid
import "github.com/google/uuid"
id := uuid.New() // v4 random
v7, _ := uuid.NewV7() // time-ordered (v1.6+)
v1, _ := uuid.NewUUID() // v1 timestamp
v5 := uuid.NewSHA1(uuid.NameSpaceURL,
[]byte("https://example.com/users/42")) // v5 deterministic
s := id.String() // canonical form
parsed, err := uuid.Parse(s) // parse, validating
uuid.UUID is [16]byte under the hood — a value type, comparable, hashable, and zero-cost to pass around.
v7 in google/uuid
NewV7() was added in v1.6.0 (December 2023). It returns an error in two
edge cases: clock skew, or random source failure. In practice, ignore the
error in most code:
v7 := uuid.Must(uuid.NewV7())
Or handle it explicitly if you’re building infrastructure code that should never panic.
High-throughput alternative: gofrs/uuid
If you’re generating millions of UUIDs per second per process, github.com/gofrs/uuid/v5 is faster and offers a wider API:
import "github.com/gofrs/uuid/v5"
id, err := uuid.NewV4()
v7, err := uuid.NewV7()
v6, err := uuid.NewV6()
v5 := uuid.NewV5(uuid.NamespaceURL, "https://example.com")
For typical web service workloads, google/uuid is fast enough. Pick gofrs/uuid only when you’ve measured a real bottleneck.
JSON serialization
UUIDs marshal as strings by default:
type User struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
}
u := User{ID: uuid.New(), Email: "alice@example.com"}
b, _ := json.Marshal(u)
// {"id":"0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b","email":"alice@example.com"}
Unmarshalling expects the canonical string and errors on invalid input.
Database integration
database/sql + pgx
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/google/uuid"
)
var id uuid.UUID
err := db.QueryRow("SELECT id FROM users WHERE email=$1", email).Scan(&id)
_, err = db.Exec("INSERT INTO users (id, email) VALUES ($1, $2)",
uuid.New(), email)
google/uuid implements sql.Scanner and driver.Valuer so it interops with database/sql natively.
sqlx
type User struct {
ID uuid.UUID `db:"id"`
Email string `db:"email"`
}
var u User
err := db.Get(&u, "SELECT id, email FROM users WHERE id=$1", id)
GORM
type User struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
Email string
}
// or, generate in Go before insert:
type User struct {
ID uuid.UUID `gorm:"type:uuid;primary_key"`
Email string
}
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.ID = uuid.Must(uuid.NewV7())
return nil
}
For PostgreSQL primary keys, generating v7 in Go (BeforeCreate hook) is
cleaner than relying on a database default — works the same on PG 13–17
without needing the new uuidv7() function.
HTTP routing
With chi:
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
// ...
})
With Go 1.22+ stdlib mux:
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := uuid.Parse(r.PathValue("id"))
// ...
})
Comparing UUIDs
UUIDs are arrays, so == works:
a := uuid.New()
b := a
a == b // true
For nil checks:
if id == uuid.Nil {
// not set
}
Bytes and byte order
b := id[:] // 16 bytes, RFC 4122 order
Slicing is zero-copy. Use this for binary protocols or bytea columns in PostgreSQL when you want to skip the canonical-string overhead.
Performance
| Approach | Throughput (single-threaded, M2 Mac) |
|---|---|
uuid.New() (v4) | ~3M/sec |
uuid.NewV7() | ~5M/sec |
gofrs/uuid.NewV4() | ~5M/sec |
gofrs/uuid.NewV7() | ~10M/sec |
UUIDs aren’t usually the bottleneck. If they are, switch to gofrs/uuid and consider batching.
Common pitfalls
uuid.New()panics on random source failure (e.g. /dev/urandom missing). In practice impossible on Linux, but if you’re on an embedded system, preferuuid.NewRandom()and handle the error.uuid.MustParsepanics on bad input. Use it only for compile-time constants. For user input, alwaysuuid.Parseand check the error.- Don’t
string(b)to convert bytes to a UUID string. That’s the byte values as a byte string, not the canonical form. Useuuid.FromBytes(b)and then.String(). - GORM’s
default:gen_random_uuid()only works on PostgreSQL. For MySQL or SQLite, generate in Go.
Cheat sheet
| Goal | Code |
|---|---|
| Random UUID | uuid.New() |
| Time-ordered (v7) | uuid.Must(uuid.NewV7()) |
| Deterministic (v5) | uuid.NewSHA1(ns, []byte(name)) |
| Parse | uuid.Parse(s) |
| Empty | uuid.Nil |
| Bytes (RFC order) | id[:] |
Try the tools
- UUID generator — generate in browser
- UUID v7 generator — modern primary keys
- UUID validator — decode and verify
- PostgreSQL UUID guide — DB-side patterns