Composite Primary Keys in Django 5.2: A Complete Guide

This article explains Django 5.2’s new support for composite primary keys. It covers how to define CompositePrimaryKey, how pk behaves as a tuple, limitations in migrations and relationships, how composite keys interact with forms and database functions, and how to properly introspect primary key fields using _meta.pk_fields.

Django 5.2 composite primary keyCompositePrimaryKey, multi-column primary keyForeignObject, pk_fields, Django models

~3 min read • Updated Mar 15, 2026

Introduction

In Django, every model has a primary key. Traditionally, this primary key is a single field. However, some database designs require a primary key composed of multiple fields. Starting with Django 5.2, Django officially supports composite primary keys.


Defining a Composite Primary Key

To define a composite primary key, set the model’s pk attribute to CompositePrimaryKey:


class OrderLineItem(models.Model):
    pk = models.CompositePrimaryKey("product_id", "order_id")
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    quantity = models.IntegerField()

This generates a database-level primary key like:


PRIMARY KEY (product_id, order_id)

How pk Works with Composite Keys

For composite primary keys, pk becomes a tuple:


item.pk
# (1, "A755H")

You can assign a tuple directly:


item = OrderLineItem(pk=(2, "B142C"))
item.product_id  # 2
item.order_id    # "B142C"

Filtering also works with tuples:


OrderLineItem.objects.filter(pk=(1, "A755H")).count()

Current Limitations

Composite primary key support is still evolving. The following features are not supported yet:

  • ForeignKey to a model with a composite primary key
  • GenericForeignKey
  • Registering such models in Django Admin

These capabilities are expected in future releases.


Migrating to a Composite Primary Key

Django cannot migrate an existing table from a single primary key to a composite one. It also cannot add or remove fields from a composite primary key.

To migrate an existing table:

  1. Modify the database schema manually according to your backend’s instructions.
  2. Add CompositePrimaryKey to your Django model.
  3. Apply migrations using --fake to avoid errors.
  4. Or use SeparateDatabaseAndState to combine manual and Django migrations.

Composite Primary Keys and Relationships

Relationship fields like ForeignKey do not support composite primary keys:


class Foo(models.Model):
    item = models.ForeignKey(OrderLineItem)  # ❌ Not supported

Workaround: ForeignObject

You can use ForeignObject instead:


class Foo(models.Model):
    item_order_id = models.CharField(max_length=20)
    item_product_id = models.IntegerField()
    item = models.ForeignObject(
        OrderLineItem,
        on_delete=models.CASCADE,
        from_fields=("item_order_id", "item_product_id"),
        to_fields=("order_id", "product_id"),
    )

Note: ForeignObject is an internal API and not covered by Django’s deprecation policy.


Composite Primary Keys and Database Functions

Many database functions accept only a single column:


Max("order_id")      # OK
Max("product_id")    # OK
Max("pk")            # ❌ ValueError
Count("pk")          # OK

This is because pk expands to multiple columns.


Composite Primary Keys in Forms

Since a composite primary key is a virtual field, it does not appear in ModelForms:



Adding pk to the form raises a FieldError.

Changing a primary key creates a new object, so it’s recommended to set editable=False on all primary key fields.


Model Validation with Composite Keys

Because pk is virtual, excluding it in clean_fields() has no effect. You must exclude each primary key field individually.

However, validate_unique() can still use exclude={"pk"}.


Introspecting Composite Primary Keys

Previously, you could detect the primary key field using field.primary_key. With composite keys, no field has primary_key=True.

Instead, use _meta.pk_fields:


Product._meta.pk_fields
# []

OrderLineItem._meta.pk_fields
# [, ]

Conclusion

Composite primary keys in Django 5.2 introduce powerful new capabilities for modeling complex database schemas. Although some limitations remain—especially around relationships, admin integration, and migrations—this feature marks a major step forward for Django’s ORM.

Written & researched by Dr. Shahin Siami