Does DynamoDB support foreign keys?
No. DynamoDB has no foreign keys, referential-integrity constraints, or cascading deletes — as a NoSQL database it never enforces relationships between items or tables. You model relationships yourself instead: denormalize related data into one item, or co-locate related items under a shared partition key with single-table design. To see and traverse those modeled relationships, DynoTable's Smart Tables draw a relationship between two tables on a canvas and browse the joined rows.
Why there are no foreign keys
A foreign key exists to support joins and enforce integrity across normalized tables. DynamoDB deliberately omits the JOIN operator (AWS recommends denormalizing instead), so a foreign-key constraint would police a relationship the query model never exploits. Nothing stops you storing another item's key as an attribute — DynamoDB just won't validate or cascade it.
How relationships are modeled instead
- Embed — small, bounded child data lives inside the parent item as a list or map.
- Co-locate — parent and children share a partition key with distinct sort keys, so one
Queryreturns the whole relationship; this is the heart of single-table design. - Duplicate — copy the fields each access pattern needs onto the items that need them, accepting write-time upkeep for single-request reads.
The one-to-many and many-to-many guides cover each shape in depth.
Enforcing integrity when it matters
For the cases where you'd have leaned on a constraint, DynamoDB gives you building blocks: condition expressions guard a write on the state of the item being written, and a transaction's ConditionCheck can verify a different item (say, the parent) exists in the same all-or-nothing operation. Cascading deletes become explicit application logic or a Streams-driven cleanup.
What that looks like when you run it
We put a PROFILE item and two ORDER# items under pk = "CUSTOMER#1", deleted the profile, and queried the partition again:
Count: 2
[{"sk":{"S":"ORDER#1"},"pk":{"S":"CUSTOMER#1"}},
{"sk":{"S":"ORDER#2"},"pk":{"S":"CUSTOMER#1"}}]The delete returned success. Two orphans, no warning, no error to catch. In PostgreSQL the same delete fails, cascades, or nulls the child reference, depending on which constraint you declared.
Then the closest replacement: a TransactWriteItems that condition-checks the parent before writing a third order.
TransactionCanceledException: Transaction cancelled, please refer cancellation
reasons for specific reasons [ConditionalCheckFailed, None]
CancellationReasons: [
{"Code":"ConditionalCheckFailed","Message":"The conditional request failed."},
{"Code":"None"}
]The array positions match your TransactItems positions, so [ConditionalCheckFailed, None] says action 0 (the parent check) failed and action 1 (the child write) was fine. With one guard that reads plainly; with eight actions, that array is the only way to find out which one broke.
It bills, too. A transactional write consumes two write units per item, and AWS is explicit that "this capacity is consumed even when the transaction is canceled". Every rejected write costs the same as an accepted one.
Go deeper
Start with single-table design, build the guarding conditions in the expression builder, and download DynoTable to browse those relationships visually — its Smart Tables join parent and child tables on a canvas so you see the whole item collection in one view.
References
- What is Amazon DynamoDB? — Amazon DynamoDB Developer Guide
- Amazon DynamoDB Transactions: How it works — Amazon DynamoDB Developer Guide
- Best practices for NoSQL design — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations — Amazon DynamoDB Developer Guide
Last verified 2026-07-13 against the official AWS documentation linked above.
The orphaned-children query and the cancellation output were reproduced 2026-07-28 against DynamoDB Local 3.3.0 with @aws-sdk/client-dynamodb 3.1095.0 on Node v24.18.0.