Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Generating UUIDs in Go

On this page
  1. Quick start with google/uuid
  2. v7 in google/uuid
  3. High-throughput alternative: gofrs/uuid
  4. JSON serialization
  5. Database integration
    1. database/sql + pgx
    2. sqlx
    3. GORM
  6. HTTP routing
  7. Comparing UUIDs
  8. Bytes and byte order
  9. Performance
  10. Common pitfalls
  11. Cheat sheet
  12. Try the tools

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

ApproachThroughput (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

Cheat sheet

GoalCode
Random UUIDuuid.New()
Time-ordered (v7)uuid.Must(uuid.NewV7())
Deterministic (v5)uuid.NewSHA1(ns, []byte(name))
Parseuuid.Parse(s)
Emptyuuid.Nil
Bytes (RFC order)id[:]

Try the tools