ShortUUID

Build Status Coverage Status Module Version Hex Docs Total Download License Last Updated

ShortUUID is a lightweight Elixir library that turns standard UUIDs into shorter strings, for example for use in URLs. You can choose from a set of predefined alphabets or define your own. The default alphabet includes lowercase letters, uppercase letters, and digits, omitting characters like 'l', '1', 'I', 'O', and '0' to keep them readable. It is URL-safe.

Not every alphabet is URL-safe: :base64 and custom alphabets may contain characters that need escaping in URLs (see Custom Alphabets).

Note: Different ShortUUID implementations are compatible as long as they use the same alphabet in the same order. There is no official standard, so if you plan to use ShortUUID with other libraries, test for compatibility. See Compatibility with other implementations.

Unlike some other solutions, ShortUUID does not produce UUIDs on its own as there are already plenty of libraries to do so. To generate UUIDs, use libraries such as Uniq (including version 7 UUIDs), Erlang UUID or Ecto, which can generate version 4 UUIDs.

ShortUUID accepts common UUID formats in any letter case, since UUIDs are case-insensitive:

"550e8400-e29b-41d4-a716-446655440000"           # with hyphens
"550e8400e29b41d4a716446655440000"               # without hyphens
"{550e8400-e29b-41d4-a716-446655440000}"         # in curly braces
"urn:uuid:550e8400-e29b-41d4-a716-446655440000"  # as a URN (RFC 9562)

Encoded short IDs are case-sensitive: "abc" and "ABC" are different IDs.

Compatibility

Compatibility with other implementations

ShortUUID encodes the most significant bits first and pads with the first alphabet character at the start, like Python's shortuuid and Node.js short-uuid. Encoded IDs are interchangeable as long as both sides use the same alphabet in the same order.

Python's shortuuid sorts custom alphabets, so only alphabets that are already sorted produce the same IDs. A mismatch is silent: the ID decodes to a different, valid UUID instead of failing.

Alphabet Python shortuuid Node.js short-uuid
:base57_shortuuid (default) yes, default alphabet yes
:base58 yes yes
:base62 yes yes
:base32_crockford yes yes
:base32_hex yes yes
:base32, :base32_z, :base64, :base64_url no, Python sorts them yes
custom alphabet only if sorted by codepoint yes, except characters outside the BMP such as emoji

ShortUUID is also stricter than both: it rejects IDs that are not exactly the encoded length or that are out of the 128-bit range, where the other libraries may return a UUID.

These results are checked by tests against fixtures generated with pinned versions of both libraries, see scripts/compat.

v4.0.0 breaking changes

Raw binary UUID input (as <<...>>) is no longer supported. UUIDs must be provided as strings, in one of the formats listed at the top of this page.

Examples of supported formats:

# Supported
"550e8400-e29b-41d4-a716-446655440000"  # With hyphens
"550e8400e29b41d4a716446655440000"      # Without hyphens

# No longer supported in v4.0.0
<<85, 14, 132, 0, 226, 155, 65, 212, 167, 22, 68, 102, 85, 68, 0, 0>>

v3.0.0 breaking changes

Changed bit order and padding behavior to align with other language implementations:

Before v3.0.0


iex> "00000001-0001-0001-0001-000000000001" |> ShortUUID.encode
{:ok, "UD6ibhr3V4YXvriP822222"}

After v3.0.0


iex> "00000001-0001-0001-0001-000000000001" |> ShortUUID.encode
{:ok, "222228PirvXY4V3rhbi6DU"}

To migrate ShortUUIDs created using < v3.0.0 reverse them before passing to decode.

# UUID "00000001-0001-0001-0001-000000000001" encoded using v2.1.2 to "UD6ibhr3V4YXvriP822222"
# reversing the encoded string before decode with v3.0.0 will produce the correct result
iex> "UD6ibhr3V4YXvriP822222" |> String.reverse() |> ShortUUID.decode!()
"00000001-0001-0001-0001-000000000001"

Warning: Decoding ShortUUIDs created using a version < v3.0.0 without reversing the string first will not fail but produce an incorrect result

iex> "UD6ibhr3V4YXvriP822222" |> ShortUUID.decode!() === "00000001-0001-0001-0001-000000000001"
false
iex> "UD6ibhr3V4YXvriP822222" |> ShortUUID.decode()
{:ok, "933997ef-eb92-293f-b202-2a879fc84be9"}

Installation

Add :shortuuid to your list of dependencies in mix.exs:

def deps do
  [
    {:shortuuid, "~> 4.3"}
  ]
end

ShortUUID has no runtime dependencies and supports Elixir 1.8 or later.

Examples

iex> "f98e80e7-9923-4173-8408-98f8254912ad" |> ShortUUID.encode
{:ok, "nQtAWSRQ6ByybDtRs7dQwE"}

iex> "f98e80e7-9923-4173-8408-98f8254912ad" |> ShortUUID.encode!
"nQtAWSRQ6ByybDtRs7dQwE"

iex> "nQtAWSRQ6ByybDtRs7dQwE" |> ShortUUID.decode
{:ok, "f98e80e7-9923-4173-8408-98f8254912ad"}

iex> "nQtAWSRQ6ByybDtRs7dQwE" |> ShortUUID.decode!
"f98e80e7-9923-4173-8408-98f8254912ad"

Using ShortUUID with Ecto

If you would like to use ShortUUID with Ecto schemas try Ecto.ShortUUID.

It provides a custom Ecto type which allows for ShortUUID primary and foreign keys while staying compatible with :binary_id (Ecto.UUID).

Custom Alphabets

Starting with version v4.0.0, ShortUUID allows you to define custom alphabets for encoding and decoding UUIDs. You can use predefined alphabets or define your own.

Alphabets made only of ASCII characters, like all the predefined ones, use a faster decoder that works byte by byte (see bench/decode.exs).

Restrictions

Combining characters and Unicode normalization

Characters are split into the alphabet by grapheme, as you see them, but encoded IDs are matched exactly by codepoint. This means alphabets may contain characters that merge visually when placed next to each other, such as combining marks, flag letters (regional indicators), emoji modifiers or \r and \n. An ID may then look shorter than its length in alphabet characters.

Some systems (browsers, databases, file systems) apply Unicode normalization, which can change the bytes of such IDs, e.g. turning e followed by a combining accent into é. A normalized ID will then fail to decode. Alphabets that could make a normalized ID decode to a different UUID are rejected at compile time. If you need IDs to survive arbitrary text processing, stick to ASCII alphabets.

URL safety

Alphabets are not required to be URL-safe. Any characters are allowed, including emoji, whitespace, control characters and characters that have a special meaning in URLs such as +, /, ?, # or %. If you put the encoded IDs into URLs, either pick a URL-safe alphabet or percent-encode them.

All predefined alphabets are URL-safe except :base64, which uses + and /. Use :base64_url instead if you need a URL-safe base64 alphabet.

Predefined Alphabets

Starting with version v4.0.0, the following predefined alphabets are available:

Using a custom or predefined alphabet

defmodule MyBase58UUID do
  use ShortUUID.Builder, alphabet: :base58
end

defmodule MyCustomUUID do
  use ShortUUID.Builder, alphabet: "0123456789ABCDEF"
end

iex> MyBase58UUID.encode("550e8400-e29b-41d4-a716-446655440000")
{:ok, "BWBeN28Vb7cMEx7Ym8AUzs"}

iex> MyBase58UUID.decode("BWBeN28Vb7cMEx7Ym8AUzs")
{:ok, "550e8400-e29b-41d4-a716-446655440000"}

Just for fun

Since v4.0.0 alphabets are not limited to alphanumeric characters either

defmodule UnicodeUUID do
  use ShortUUID.Builder, alphabet: "🌟💫✨⭐️🌙🌎🌍🌏🌑🌒🌓🌔🌕🌖🌗🌘"
end

iex> UnicodeUUID.encode("550e8400-e29b-41d4-a716-446655440000")
{:ok, "🌎🌎🌟🌗🌑🌙🌟🌟🌗✨🌒🌔🌙💫🌖🌙🌓🌏💫🌍🌙🌙🌍🌍🌎🌎🌙🌙🌟🌟🌟🌟"}


defmodule SmileyUUID do
  use ShortUUID.Builder, alphabet: "😀😊😄😍🥰😘😜🤪😋🤔😌🧐😐😑😶😮😲😱😴🥱😪😢😭😤😎🤓😇😈👻👽🤖🤡💀"
end

iex> SmileyUUID.encode("550e8400-e29b-41d4-a716-446655440000")
{:ok, "😊🤪😢😘💀🥰😲😊🤡🤖🤔😊😘😤👽🤓👻😊👽😲😋😀😭😇😲🤖"}

Implementing Compatible Modules

Starting with version v4.1.0, ShortUUID provides a behaviour that you can implement to create compatible modules. This is useful if you want to create your own ShortUUID-compatible module with custom functionality while maintaining a consistent interface.

Modules created with ShortUUID.Builder also define __shortuuid__/0, which returns their alphabet, base, encoded length and canonical: true: every UUID has exactly one short ID, and decode/1 accepts nothing else. Libraries such as ecto_shortuuid can use this to skip re-encoding decoded IDs to check them.

A common reason to write one by hand is a prefixed ID, like usr_H9cNmGXLEc8NWcZzSThA9S, which ShortUUID.Builder can't produce. This module wraps ShortUUID and adds a fixed prefix:

defmodule MyApp.UserID do
  @behaviour ShortUUID.Behaviour
  @prefix "usr_"

  @impl true
  def encode(uuid) do
    with {:ok, short} <- ShortUUID.encode(uuid), do: {:ok, @prefix <> short}
  end

  @impl true
  def encode!(uuid) do
    case encode(uuid) do
      {:ok, encoded} -> encoded
      {:error, msg} -> raise ArgumentError, message: msg
    end
  end

  @impl true
  def decode(@prefix <> short), do: ShortUUID.decode(short)
  def decode(_), do: {:error, "Invalid user ID"}

  @impl true
  def decode!(encoded) do
    case decode(encoded) do
      {:ok, decoded} -> decoded
      {:error, msg} -> raise ArgumentError, message: msg
    end
  end
end

It is strict by construction: the prefix is fixed and ShortUUID.decode/1 accepts only the one short ID for each UUID, so every UUID has exactly one user ID. Anything else, such as another prefix, a missing prefix, a raw UUID after the prefix or a changed letter case, returns {:error, message}. Keep that property when you write your own: a decode/1 that accepts more than encode/1 produces lets several strings stand for the same ID.

A hand-written module can also define __shortuuid__/0 with canonical: true so libraries like ecto_shortuuid skip their re-encode check. Only do this if decode/1 really accepts exactly one spelling per UUID, as above; a module that accepts any other input must leave it out.

The ShortUUID.Behaviour requires implementing four callbacks:

Using this behaviour ensures that your custom implementation will be compatible with code expecting a ShortUUID-compatible module.

Documentation

Look up the full documentation at https://hexdocs.pm/shortuuid.

Acknowledgments

Inspired by shortuuid.

Copyright (c) 2017-2026 Goran Pedić

This work is free. You can redistribute it and/or modify it under the terms of the MIT License.

See the LICENSE.md file for more details.