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.

Inspecting what LastEvaluatedKey actually contains for your table or index takes the guesswork out — the DynoTable desktop app pages through queries and shows the raw cursor, and the DynamoDB Expression Builder composes the query request around it.

References

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

Work with DynamoDB without the Console

DynoTable is a fast desktop client for DynamoDB — browse tables, run SQL-style queries, and edit items locally.