The provided starting key is invalid

TL;DR — Your ExclusiveStartKey doesn't match the key schema of what you're paginating. It must contain exactly the key attributes DynamoDB returned in LastEvaluatedKey — the full primary key, plus the index key when you're querying a GSI/LSI — with the same names and types. Pass LastEvaluatedKey back verbatim; hand-assembling it is how this breaks.

What it means

ValidationException: The provided starting key is invalid
ValidationException: Exclusive Start Key must have same size as table's key schema

ExclusiveStartKey tells a Query/Scan where to resume. DynamoDB validates it against the key schema of the table — or, for an index query, the combined index-plus-table key that LastEvaluatedKey carries. Missing attributes, extra attributes, wrong names, or wrong types all fail before any data is read.

Why it happens

  • Hand-building the key with only the partition key — a composite-key table needs partition and sort key in the starting key.
  • Paginating an index with just the table keyLastEvaluatedKey on a GSI/LSI query contains both the index key attributes and the table's primary key; all of them must come back.
  • Type or name drift — the key was serialized (JSON, URL param, cache) and came back with "42" where LastEvaluatedKey had a number, or with a renamed field.
  • Reusing a key across queries — a LastEvaluatedKey from one table/index passed to a query against another, or to the same query after the key schema assumption changed.
  • A wrapper injecting defaults — an ODM that fills in attributes it thinks belong in the key can pad the starting key past the schema size.

How to fix it

  1. Round-trip LastEvaluatedKey untouched:

    let ExclusiveStartKey;
    do {
      const page = await docClient.send(
        new QueryCommand({
          TableName,
          KeyConditionExpression,
          ExpressionAttributeValues,
          ExclusiveStartKey
        })
      );
      items.push(...(page.Items ?? []));
      ExclusiveStartKey = page.LastEvaluatedKey; // verbatim — no rebuild
    } while (ExclusiveStartKey);
  2. Serialize it losslessly if it crosses a request boundary — when the pagination cursor goes to a browser and back, encode the whole LastEvaluatedKey object (e.g. base64 of its JSON) instead of reconstructing it from item fields, and keep number types numbers.

  3. Include every key attribute for index pagination — index partition/sort key and table partition/sort key, exactly as returned.

  4. Don't fabricate a starting point — DynamoDB pagination has no offset; if you need "start near X", express it in the KeyConditionExpression (sk > :x) rather than a hand-made ExclusiveStartKey.

  5. Log the raw LastEvaluatedKey on failure. Compare it byte-for-byte to what your next request sends — serialization layers often rename or stringify number types.

Query it in DynoTable

Page through a Query in DynoTable and inspect the raw LastEvaluatedKey cursor between pages — open the table with ⌘K, run a Query, and copy the pagination token verbatim into your SDK loop. The query panel shows exactly which key attributes DynamoDB expects.

Use the Query Builder to generate a paginated Query program with correct ExclusiveStartKey handling. Switch profiles with ⌘P; Test Connection on Settings → Profiles. See Connect to AWS and Install.

Sources

References

Last verified 2026-07-13 against the official AWS documentation linked above.

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.