Intermediate8 min read

DynamoDB Limits and Quotas, Verified Against the Live Service

What are the DynamoDB limits?

An item is capped at 400 KB, a partition key at 2,048 bytes and a sort key at 1,024 bytes. A batch write takes 25 items, a batch get 100 keys, a transaction 100 actions. A table gets 20 global and 5 local secondary indexes. Every one of those numbers on this page was established by sending the request to Amazon DynamoDB and reading what came back.

How these numbers were established

AWS publishes its quotas without evidence, and that is normally fine — until a number is load-bearing for a design decision and you want to know whether it means 400,000 bytes or 409,600, whether it counts your attribute names, and what exactly the service says when you cross it.

So we probed them. For every limit in the first table below, a request was built to sit exactly on the documented value and sent to the live service in us-east-1; then a second request, one unit past it. The first must be accepted and the second rejected — that pair is what locates the edge, rather than taking the documentation's word for it. The rejection message in the last column is the service's own sentence, captured verbatim and never retyped.

Four rows could only be established from the rejection side. Those are CreateTable limits whose accept side would mean building a table with twenty indexes and waiting for each to become active, for a number the rejection states outright. The How it was established column says which is which; it is not decoration.

Verified limits

LimitValueHow it was establishedWhat the service returns when you cross it
Maximum item size409,600 bytesAccepted at 409,600, rejected at 409,601ValidationException: Item size has exceeded the maximum allowed size
Maximum partition key value2,048 bytesAccepted at 2,048, rejected at 2,049ValidationException: One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes
Maximum sort key value1,024 bytesAccepted at 1,024, rejected at 1,025ValidationException: One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes
Maximum nesting depth32 levelsAccepted at 32, rejected at 33ValidationException: 1 validation error detected: Nesting Levels have exceeded supported limits: Attributes in the item have nested levels beyond supported limit
Maximum expression length4,096 bytesAccepted at 4,096, rejected at 4,097ValidationException: 1 validation error detected: Invalid ConditionExpression: Expression size has exceeded the maximum allowed size;
Maximum items per BatchWriteItem25 itemsAccepted at 25, rejected at 26ValidationException: 1 validation error detected: Value '<your request>' at 'requestItems' failed to satisfy constraint: Map value must satisfy constraint: [Member must have length less than or equal to 25, Member must have length greater than or equal to 1]
Maximum keys per BatchGetItem100 itemsAccepted at 100, rejected at 101ValidationException: 1 validation error detected: Value at 'RequestItems.<table-name>.member.Keys' failed to satisfy constraint: Member must have length less than or equal to 100
Maximum actions per TransactWriteItems100 itemsAccepted at 100, rejected at 101ValidationException: 1 validation error detected: Value '<your request>' at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 100
Global secondary indexes per table20Rejection onlyValidationException: One or more parameter values were invalid: GlobalSecondaryIndex count exceeds the per-table limit of 20
Local secondary indexes per table5Rejection onlyValidationException: One or more parameter values were invalid: Number of LocalSecondaryIndexes exceeds per-table limit of 5
Projected non-key attributes per index20Rejection onlyValidationException: 1 validation error detected: Value '<your request>' at 'globalSecondaryIndexes.1.member.projection.nonKeyAttributes' failed to satisfy constraint: Member must have length less than or equal to 20
Projected non-key attributes per table100Rejection onlyValidationException: One or more parameter values were invalid: Number of projected attributes in all indexes exceeds limit of 100, number of projected attributes:120

Environment: Amazon DynamoDB, live service, us-east-1, probed 2026-08-27 with the AWS SDK for JavaScript v3.

What the probes turned up

400 KB means 409,600 bytes, and it counts your attribute names. An item measured at exactly 409,600 bytes was accepted; 409,601 was rejected. The measurement counts the UTF-8 length of every attribute name plus every value, which is the same accounting our item size calculator implements — the probe builds its payload with that library, so the two agree by construction rather than by assertion. The deeper modelling problem behind this limit has its own guide: the DynamoDB item size limit.

The projected-attributes limit everyone quotes is the wrong one. The AWS Quotas page documents one figure — "up to 100 attributes combined for all of a table's local and global secondary indexes" — and never mentions a per-index cap. There is one, and it is 20. A CreateTable projecting 21 non-key attributes into a single index is rejected before the per-table total is anywhere near 100. The 20 is documented, but only in the API Reference's Projection page, as an array-member constraint: "Maximum number of 20 items". If you plan an index against the Quotas page alone, the API will refuse a schema the Quotas page says is fine. Both numbers are in the table above, each with the rejection that proves it.

Two of the messages contain AWS's own typos, reproduced here rather than silently corrected — maximum size limit of2048 bytes is missing a space, and number of projected attributes:120 is missing another. If you are grepping logs for these strings, grep for what the service sends, not for what reads correctly.

Sort keys are measured in aggregate. The sort-key rejection says "Aggregated size of all range keys", not "the sort key", because the same 1,024-byte budget covers the sort key of the table and of every local secondary index the item lands in.

The 1 MB page, which raises nothing at all

Every limit above announces itself by rejecting you. The Query and Scan page limit does not. Cross it and DynamoDB returns a short page and a LastEvaluatedKey, with no error and no warning — which is why "my Scan only returned part of the table" is such a common surprise, and why pagination is not optional.

That also means there is no error message to quote, so it was measured instead of provoked:

At 1,000 bytes an item, a page held 1,029 items and returned a LastEvaluatedKey — 1,029,000 bytes of item data, with the 1,030th item left for the next request. At 5,000 bytes an item, a page held 208 items and returned a LastEvaluatedKey — 1,040,000 bytes of item data, with the 209th item left for the next request.

Neither page held 1 MiB of item data — the first fell about 19,576 bytes short. So the page budget charges more per item than the item's own bytes.

Two probes at different item sizes are enough to pin that down. Treating a page as items × (item bytes + per-item overhead) ≤ budget, only 7 whole-byte overheads are consistent with both measurements, and exactly one of them puts the budget on a round binary megabyte: an overhead of 19 bytes an item, with the budget between 1,048,551 and 1,048,971 bytes — a range containing 1,048,576. DynamoDB's "1 MB" is binary, as its own quotas page states, and it is spent on item bytes plus per-item overhead. Budget for roughly 19 bytes of it per item.

Quotas we did not probe

The limits below are cited to AWS, not measured. They are account-level quotas: most are adjustable on request, and reaching them means provisioning throughput that bills by the hour, creating thousands of tables, or committing to a year of reserved capacity. None of that is a probe, so none of it is presented as one. The source for every row is the AWS Quotas in Amazon DynamoDB page.

QuotaDefault valueAdjustableWhy we did not probe it
Tables per account per Region2,500YesCreating 2,500 tables to watch the 2,501st fail leaves an account someone has to unwind.
Provisioned throughput per table40,000 RCU and 40,000 WCUYesProvisioning 40,000 units bills by the hour whether or not a single request is made.
Provisioned throughput per account80,000 RCU and 80,000 WCUYesSame reason, twice over — and it changes an account-wide setting.
On-demand throughput per table40,000 RRU and 40,000 WRUYesReaching it means sustaining 40,000 requests a second, which is a load test with a bill.
Active reserved capacity per account1,000,000 capacity unitsYesReserved capacity is a one-year purchase commitment, not a probe.
Table sizeNo practical limitAWS states tables are unconstrained in items and bytes; there is no edge to find.

Which limits you should design around

Most of these you will never meet. The handful that shape real designs:

  • 400 KB per item is a modelling constraint, not a quota. An item that approaches it is usually an unbounded one-to-many relationship stored as an embedded list. See the item size limit.
  • The 1 MB page governs every Query and Scan you write. Code that ignores LastEvaluatedKey is quietly wrong the day your data outgrows one page.
  • 25 items per batch write and 100 per batch get shape your bulk-load loops. See batch operations.
  • 100 actions per transaction is the one people hit while trying to make DynamoDB behave relationally. See transactions.
  • 20 GSIs, 5 LSIs, 100 projected attributes constrain access-pattern design far more often than the throughput quotas do, and the LSI count is fixed the moment the table is created. See index projections and GSI vs LSI.

Most of the rejections quoted above also have a page of their own under DynamoDB errors, with the request that produces each one. The four CreateTable rejections do not — they were captured for this page.

To check a single item against the 400 KB line without writing it, the item size calculator runs in your browser. To look at the items in your own tables, DynoTable is a desktop DynamoDB client.

Updated