Each note in a note-taking application needs an ID. If every write goes through a database, an auto-incrementing key may be enough. If clients create notes offline or several services generate IDs independently, we need another strategy.
Random IDs avoid coordination, but collisions are improbable rather than impossible. Keep a uniqueness constraint where IDs are stored and handle a collision if one occurs.
UUIDs in the standard library
Go 1.27 added the
uuid package. It supports random version 4 UUIDs and time-ordered version 7 UUIDs, standardized in
RFC 9562.
This complete example requires Go 1.27 or later:
package main
import (
"fmt"
"uuid"
)
func main() {
fmt.Println("random:", uuid.NewV4())
fmt.Println("time-ordered:", uuid.NewV7())
}
Version 4 contains 122 random bits; the remaining bits identify the format. Version 7 includes a timestamp, making it useful when keys should cluster by creation time. Do not interpret that order as a global event ordering across machines: clocks can differ or move backward.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
randomID, err := uuid.NewRandom()
if err != nil {
log.Fatal(err)
}
orderedID, err := uuid.NewV7()
if err != nil {
log.Fatal(err)
}
fmt.Println(randomID)
fmt.Println(orderedID)
}
Add that dependency with go get github.com/google/uuid. Its API is different from the standard library’s: for example, its NewV7 returns an error as well as the ID.
UUIDs are not the only choice. These formats have different tradeoffs:
| Format |
Text length |
Main properties |
| UUID |
36 |
Standard format; choose v4 for random IDs or v7 for time clustering |
| ULID |
26 |
Millisecond timestamp and randomness; sortable Base32 text |
| KSUID |
27 |
Timestamp and randomness; sortable Base62 text |
| XID |
20 |
Timestamp, machine/process information, and counter; compact Base32 text |
This is a survey of formats, not a guarantee that every format is suitable for every deployment. XID and Snowflake-style generators have different machine-identity and coordination assumptions from a random UUID. Time-based formats also disclose approximate creation time.
For ULIDs, use a cryptographic entropy source rather than constructing a new time-seeded math/rand generator for each ID:
package main
import (
"crypto/rand"
"fmt"
"log"
"time"
"github.com/oklog/ulid/v2"
)
func main() {
id, err := ulid.New(ulid.Timestamp(time.Now()), rand.Reader)
if err != nil {
log.Fatal(err)
}
fmt.Println(id)
}
Install with go get github.com/oklog/ulid/v2. Random ULIDs created in the same millisecond do not necessarily sort in generation order; use the library’s monotonic entropy facility if that property is required, and follow its concurrency contract.
The site’s
ID comparison page shows several of the libraries from the original survey. Its set of generators differs from the examples here.
IDs and secret tokens serve different purposes
An ID identifies an object. Access control must still decide who can read or change it. A sortable ID, especially one with timestamps or counters, should not be treated as an authentication secret.
token := rand.Text() // import "crypto/rand"
It returns a URL-safe Base32 string with at least 128 bits of randomness. Its length may increase in future releases, so do not hard-code the current length in a storage schema.
For new code I would start with UUID v4 or v7, then choose a shorter format only if its size or sorting behavior solves a concrete requirement. The
original example code records the libraries used in the 2017 survey.