Skip to content

Overloaded Keys

Overloaded keys is the naming convention in single-table DynamoDB design where generic column names (pk, sk, gsi1pk, gsi1sk) store different logical values depending on the entity type of each item.

The convention

In a relational database, column names describe what they contain: customer_id, order_date, status. In single-table DynamoDB, the same physical columns store conceptually different things:

Customer item:   pk = "CUSTOMER#cust_01",  sk = "#METADATA"
Order item:      pk = "ORDER#ord_01",      sk = "#METADATA"
OrderItem:       pk = "ORDER#ord_01",      sk = "ITEM#prod_42"
Membership:      pk = "TEAM#t_eng",        sk = "MEMBER#u_alice"

The pk column is “overloaded” — it means customerId for customer records, orderId for orders, teamId for memberships. The actual semantic is encoded in the value (the type prefix) rather than the column name.

Why generic names

If you named the primary key customerId, DynamoDB would enforce that every item has a customerId — which makes it impossible to store orders, products, or other entity types in the same table. Generic names (pk, sk) make no such assumption, enabling all entity types to share the table.

The type prefix convention

Entity type is encoded in the partition key value using a prefix:

"CUSTOMER#<id>"  → customer records
"ORDER#<id>"     → order records
"TENANT#<id>"    → tenant records (for SaaS)
"USER#<id>"      → user records
"PRODUCT#<id>"   → product records

The prefix is what allows DynamoDB to distinguish entity types within the same table — and within the same partition. The sort key uses the same convention:

"#METADATA"          → entity's own record
"ORDER#<ulid>"       → order record within a customer partition
"MEMBER#<userId>"    → membership record within a team partition

The single-table design mistake: no prefix

The most common single-table design mistake is using raw IDs without prefixes:

Customer: pk = "cust_01",  sk = "metadata"
Order:    pk = "ord_01",   sk = "metadata"

This works until two entity types accidentally share the same ID space, or until you need to distinguish entity types from a GSI result that returns multiple types. The type prefix is non-optional in production single-table designs. See the 5 most common mistakes for why this matters.

ORMs and overloaded keys

ElectroDB manages overloaded keys via key templates:

pk: { field: "pk", composite: ["customerId"], template: "CUSTOMER#${customerId}" }

The template applies the prefix automatically on write and strips it on read. Your application code works with customerId, orderId — not "CUSTOMER#cust_01". The ORM handles the translation.

← All glossary terms