Skip to main content

Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.

Project description

django-display-ids

Stripe-like prefixed IDs for Django. Works with existing UUIDs — no schema changes.

Display IDs are human-friendly identifiers like inv_2aUyqjCzEIiEcYMKj7TZtw — a short prefix indicating the object type, followed by a base62-encoded UUID. This format, popularized by Stripe, makes IDs recognizable at a glance while remaining URL-safe and compact.

This library focuses on lookup only — it works with your existing UUID fields and requires no migrations or schema changes.

Installation

pip install django-display-ids

No INSTALLED_APPS entry required — just import and use.

Quick Start

from django.views.generic import DetailView
from django_display_ids import DisplayIDObjectMixin

class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
    model = Invoice
    lookup_param = "id"
    lookup_strategies = ("display_id", "uuid")
    display_id_prefix = "inv"
# urls.py
urlpatterns = [
    path("invoices/<str:id>/", InvoiceDetailView.as_view()),
]

Now your view accepts both formats:

  • inv_2aUyqjCzEIiEcYMKj7TZtw (display ID)
  • 550e8400-e29b-41d4-a716-446655440000 (UUID)

Features

  • Multiple identifier formats: display ID (prefix_base62uuid), UUID (v4/v7), slug
  • Framework support: Django CBVs and Django REST Framework
  • Zero model changes required: Works with any existing UUID field
  • Stateless: Pure lookup, no database writes

Usage

Django Class-Based Views

from django.views.generic import DetailView, UpdateView, DeleteView
from django_display_ids import DisplayIDObjectMixin

class InvoiceDetailView(DisplayIDObjectMixin, DetailView):
    model = Invoice
    lookup_param = "id"
    lookup_strategies = ("display_id", "uuid")
    display_id_prefix = "inv"

# Works with any view that uses get_object()
class InvoiceUpdateView(DisplayIDObjectMixin, UpdateView):
    model = Invoice
    lookup_param = "id"
    display_id_prefix = "inv"

Django REST Framework

from rest_framework.viewsets import ModelViewSet
from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin

class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
    queryset = Invoice.objects.all()
    serializer_class = InvoiceSerializer
    lookup_url_kwarg = "id"
    lookup_strategies = ("display_id", "uuid")
    display_id_prefix = "inv"

Or with APIView:

from rest_framework.views import APIView
from rest_framework.response import Response
from django_display_ids.contrib.rest_framework import DisplayIDLookupMixin

class InvoiceView(DisplayIDLookupMixin, APIView):
    lookup_url_kwarg = "id"
    lookup_strategies = ("display_id", "uuid")
    display_id_prefix = "inv"

    def get_queryset(self):
        return Invoice.objects.all()

    def get(self, request, *args, **kwargs):
        invoice = self.get_object()
        return Response({"id": str(invoice.id)})

Serializer Field

Include display_id in your API responses:

from rest_framework import serializers
from django_display_ids.contrib.rest_framework import DisplayIDField

class InvoiceSerializer(serializers.Serializer):
    id = serializers.UUIDField(read_only=True)
    display_id = DisplayIDField()
    name = serializers.CharField()

# Output: {"id": "...", "display_id": "inv_2aUyqjCzEIiEcYMKj7TZtw", ...}

The field reads display_id_prefix from the model. You can override it:

display_id = DisplayIDField(prefix="inv")  # Use custom prefix

Prefix must be 1-16 lowercase letters. Invalid prefixes raise ValueError at initialization.

OpenAPI/drf-spectacular: When drf-spectacular is installed, the field automatically generates proper schema with prefix-specific examples (e.g., inv_2aUyqjCzEIiEcYMKj7TZtw). The prefix is resolved from (in order): field's prefix= argument, serializer's Meta.model.display_id_prefix, or the view's queryset model.

OpenAPI Parameter Descriptions

For consistent API documentation, use the provided description helpers:

from django_display_ids.contrib.rest_framework import id_param_description
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes

@extend_schema(
    parameters=[
        OpenApiParameter(
            "id",
            OpenApiTypes.STR,
            OpenApiParameter.PATH,
            description=id_param_description("inv"),
            # -> "Identifier: display_id (inv_xxx) or UUID"
        )
    ],
)
class InvoiceViewSet(DisplayIDLookupMixin, ModelViewSet):
    ...

For endpoints that also accept slugs:

description=id_param_description("app", with_slug=True)
# -> "Identifier: display_id (app_xxx), UUID, or slug"

Generic constants are also available:

from django_display_ids.contrib.rest_framework import (
    ID_PARAM_DESCRIPTION,           # "Identifier: display_id (prefix_xxx) or UUID"
    ID_PARAM_DESCRIPTION_WITH_SLUG, # "Identifier: display_id (prefix_xxx), UUID, or slug"
)

Deterministic Examples for OpenAPI

Generate consistent example UUIDs and display IDs for OpenAPI schemas:

from django_display_ids import example_uuid, example_display_id

# Generate deterministic UUID for a prefix
example_uuid("inv")
# -> UUID('a172cedc-ae47-474b-615c-54d510a5d84a')

# Generate deterministic display ID
example_display_id("inv")
# -> "inv_4ueEO5Nz4X7u9qc3FVHokM"

# Also works with model classes
example_uuid(Invoice)  # Uses Invoice.display_id_prefix

The same prefix always produces the same example, ensuring consistent documentation across regenerations.

Model Mixin

Add a display_id property to your models:

import uuid
from django.db import models
from django_display_ids import DisplayIDMixin

class Invoice(DisplayIDMixin, models.Model):
    display_id_prefix = "inv"
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)

invoice = Invoice.objects.first()
invoice.display_id  # -> "inv_2aUyqjCzEIiEcYMKj7TZtw"

Model Manager

from django_display_ids import DisplayIDMixin, DisplayIDManager

class Invoice(DisplayIDMixin, models.Model):
    display_id_prefix = "inv"
    objects = DisplayIDManager()
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)

# Get by display ID
invoice = Invoice.objects.get_by_display_id("inv_2aUyqjCzEIiEcYMKj7TZtw")

# Get by any identifier type
invoice = Invoice.objects.get_by_identifier("inv_2aUyqjCzEIiEcYMKj7TZtw")
invoice = Invoice.objects.get_by_identifier("550e8400-e29b-41d4-a716-446655440000")

# Works with filtered querysets
invoice = Invoice.objects.filter(active=True).get_by_identifier("inv_xxx")

Django Admin

Enable searching by display ID or raw UUID in the admin:

from django.contrib import admin
from django_display_ids import DisplayIDSearchMixin

@admin.register(Invoice)
class InvoiceAdmin(DisplayIDSearchMixin, admin.ModelAdmin):
    list_display = ["id", "display_id", "name", "created"]
    search_fields = ["name"]  # display_id/UUID search is automatic

Now you can search by either format in the admin search box:

  • inv_2aUyqjCzEIiEcYMKj7TZtw (display ID)
  • 550e8400-e29b-41d4-a716-446655440000 (raw UUID from logs)

The mixin automatically detects the UUID field from your model's uuid_field attribute (if using DisplayIDMixin), or defaults to id. Override with:

class InvoiceAdmin(DisplayIDSearchMixin, admin.ModelAdmin):
    uuid_field = "uid"  # custom UUID field name

Encoding and Decoding

import uuid
from django_display_ids import encode_display_id, decode_display_id

# Create a display ID from a UUID
invoice_id = uuid.uuid4()
display_id = encode_display_id("inv", invoice_id)
# -> "inv_2aUyqjCzEIiEcYMKj7TZtw"

# Decode back to prefix and UUID
prefix, decoded_uuid = decode_display_id(display_id)

Direct Resolution

from django_display_ids import resolve_object

invoice = resolve_object(
    model=Invoice,
    value="inv_2aUyqjCzEIiEcYMKj7TZtw",
    strategies=("display_id", "uuid", "slug"),
    prefix="inv",
)

Identifier Formats

Format Example Description
Display ID inv_2aUyqjCzEIiEcYMKj7TZtw Prefix + base62-encoded UUID
UUID 550e8400-e29b-41d4-a716-446655440000 Standard UUID (v4/v7)
Slug my-invoice-slug Human-readable identifier

Display ID format:

  • Prefix: 1-16 lowercase letters
  • Separator: underscore
  • Encoded UUID: 22 base62 characters (fixed length)

Lookup Strategies

Strategies are tried in order. The first successful match is returned.

Strategy Description
display_id Decode display ID, lookup by UUID field
uuid Parse as UUID, lookup by UUID field
slug Lookup by slug field

Default: ("display_id", "uuid")

The slug strategy is a catch-all, so it should always be last.

The display_id strategy requires a prefix. If no prefix is configured, the strategy is skipped.

Configuration

View/Mixin Attributes

Attribute Default Description
lookup_param / lookup_url_kwarg "pk" URL parameter name
lookup_strategies from settings Strategies to try
display_id_prefix from model Expected prefix (falls back to model's display_id_prefix)
uuid_field "id" UUID field name on model
slug_field "slug" Slug field name on model

Django Settings (Optional)

All settings have sensible defaults. Only add this if you need to override them:

# settings.py
DISPLAY_IDS = {
    "UUID_FIELD": "id",                     # default
    "SLUG_FIELD": "slug",                   # default
    "STRATEGIES": ("display_id", "uuid"),   # default
}

Error Handling

Exception When Raised
InvalidIdentifierError Identifier cannot be parsed
UnknownPrefixError Display ID prefix doesn't match expected
ObjectNotFoundError No matching database record

In views, errors are converted to HTTP responses:

  • Django CBV: Http404
  • DRF: NotFound (404) or ParseError (400)

Requirements

  • Python 3.12+
  • Django 4.2+
  • Django REST Framework 3.14+ (optional)

Development

Clone the repository and install dependencies:

git clone https://github.com/josephabrahams/django-display-ids.git
cd django-display-ids
uv sync

Run tests:

uv run pytest

Run tests with coverage:

uv run pytest --cov=src/django_display_ids

Run tests across Python and Django versions:

uvx nox

Lint and format:

uvx pre-commit run --all-files

Related Projects

If you need ID generation and storage (custom model fields), consider these alternatives:

  • django-prefix-id — PrefixIDField that generates and stores base62-encoded UUIDs
  • django-spicy-id — Drop-in AutoField replacement that displays numeric IDs as prefixed strings
  • django-charid-field — CharField wrapper supporting cuid, ksuid, ulid, and other generators

django-display-ids takes a different approach: it works with your existing UUID fields and handles resolution only. No migrations, no schema changes — just add the mixin to your views.

License

ISC

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_display_ids-0.1.4.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_display_ids-0.1.4-py3-none-any.whl (27.1 kB view details)

Uploaded Python 3

File details

Details for the file django_display_ids-0.1.4.tar.gz.

File metadata

  • Download URL: django_display_ids-0.1.4.tar.gz
  • Upload date:
  • Size: 17.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_display_ids-0.1.4.tar.gz
Algorithm Hash digest
SHA256 152e1fff3e7cdd04b7eaee9b26582eb5b155eda2312187d355c4613e4379f0a6
MD5 1dac6f7dfd3fbdc0dbcda53390c2596c
BLAKE2b-256 2db1b33cb6cb0167113cd13c751c0f4cbdecfb7b1c064eebe2041aa760bd475d

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_display_ids-0.1.4.tar.gz:

Publisher: ci.yml on josephabrahams/django-display-ids

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_display_ids-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for django_display_ids-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 88748265e6c70a7d5b2d5d6e2ccfbcbaa3306d68c0af5947515d1455913cefea
MD5 0b366a6acf14a0876690c0ffcdbb7078
BLAKE2b-256 c68fd52b6a56da84e1a2f3c7186652f4e06f200c75352c35eafd6df80410f76b

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_display_ids-0.1.4-py3-none-any.whl:

Publisher: ci.yml on josephabrahams/django-display-ids

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page