DjangoCon Europe 2025 took place in Dublin, Ireland from June 4–8, 2025. Sessions on database design returned to the question of primary key choice — a topic that has gained urgency as Django projects scale and as RFC 9562 has made UUID v7 the recommended format for new work.
Django’s UUIDField and the v4 default
Django’s UUIDField is the standard way to use UUIDs as primary keys:
import uuid
from django.db import models
class Order(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
total_cents = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
uuid.uuid4 is callable and is invoked once per row at insert time. The value is stored as PostgreSQL’s native uuid type (16 bytes), not as a string.
DjangoCon sessions noted that this pattern works well but has a hidden cost: UUID v4’s random distribution fragments B-tree indexes as tables grow past a few million rows. The fragmentation causes every insert to touch a different leaf page, driving up I/O and reducing cache efficiency.
Switching to UUID v7
The uuid6 package (also available at github.com/oittaa/uuid6-python) provides RFC 9562-compliant UUID v7 generation for Python 3.8+. Install it:
pip install uuid6
Use it as a Django model default:
import uuid6
from django.db import models
class Order(models.Model):
id = models.UUIDField(primary_key=True, default=uuid6.uuid7, editable=False)
total_cents = models.IntegerField()
class Meta:
ordering = ['id'] # chronological — no created_at index needed
uuid6.uuid7 is a callable that returns a uuid.UUID object with a v7 bit pattern. Django’s ORM and PostgreSQL driver handle the rest.
Migration from UUID v4 to UUID v7
A recurring question at DjangoCon was how to migrate existing tables. The safe approach presented:
Step 1 — add a new UUID v7 field:
class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='order',
name='id_v7',
field=models.UUIDField(default=uuid6.uuid7, editable=False, null=True),
),
]
Step 2 — backfill with a data migration:
def backfill_uuid_v7(apps, schema_editor):
Order = apps.get_model('myapp', 'Order')
for order in Order.objects.all().iterator(chunk_size=1000):
order.id_v7 = uuid6.uuid7()
order.save(update_fields=['id_v7'])
class Migration(migrations.Migration):
operations = [migrations.RunPython(backfill_uuid_v7)]
Step 3 — promote to primary key (requires dropping the existing PK constraint and adding a new one — consult the Django migrations documentation for zero-downtime patterns).
Django REST Framework serialization
UUIDField serializes to a hyphenated lowercase string in Django REST Framework by default. UUID v7 values serialize identically to v4 — no serializer changes required:
from rest_framework import serializers
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'total_cents']
# id serializes as "018fbe3a-4c5d-7b12-8abc-0123456789ab"
Python 3.13 and the standard library
DjangoCon sessions also previewed Python 3.13’s addition of uuid.uuid7() to the standard library — eliminating the need for the uuid6 third-party package for most use cases. See Python 3.13 UUID7 in the Standard Library for details.
Related resources
- UUID in Python — full guide to UUID generation and storage in Python
- UUID in Databases — B-tree index performance and storage types
- UUID v4 vs v7 — when to use each version
- UUID v7 Generator — generate RFC 9562 v7 values in your browser
External references
- uuid6 on PyPI — UUID v6 and v7 for Python 3.8+
- Django UUIDField documentation — official reference for UUID primary keys in Django models
- Django migrations documentation — data migrations, RunPython, and zero-downtime patterns
- RFC 9562 — UUID specification — the IETF standard defining UUID v7
Frequently asked questions
Does Django support UUID primary keys natively?
Yes. Django has a built-in UUIDField and you can set default=uuid.uuid4 for v4 generation. For UUID v7, pass a callable that returns a UUID v7 string using the uuid6 package: default=uuid6.uuid7. The field stores 32 hex characters in PostgreSQL's native uuid type.
How do I migrate an existing Django model from auto-increment to UUID primary key?
Add a new UUIDField with unique=True alongside the existing integer primary key. Populate it with a data migration. Update all foreign keys pointing to the table. Switch the new field to primary_key=True and remove the old integer field. This multi-step approach avoids locking the table in production. The Django migrations documentation covers data migrations and RunPython in detail.
Can Django's ORM sort by UUID v7 chronologically?
Yes. Because UUID v7 sorts lexicographically in creation order, Model.objects.order_by('id') gives chronological order for UUID v7 primary keys. This eliminates the need for a separate created_at index on write-heavy tables.