DynamoDB Query a GSI in Node.js (AWS SDK v3)

A GSI query is a normal Query plus IndexName, and then two things stop behaving like a table query: the consistency flag you are used to becomes an error, and the pagination cursor grows an extra attribute. Here AlbumTitle-index fetches songs by album, which the base table (Artist + SongTitle) cannot do without a scan.

Code

import {DynamoDBClient, QueryCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

const items = [];
let lastEvaluatedKey;

do {
  const response = await client.send(
    new QueryCommand({
      TableName: 'Music',
      IndexName: 'AlbumTitle-index',
      KeyConditionExpression: '#hashKey = :hashKeyValue',
      ExpressionAttributeNames: {
        '#hashKey': 'AlbumTitle'
      },
      ExpressionAttributeValues: {
        ':hashKeyValue': {S: 'Danzon'}
      },
      ExclusiveStartKey: lastEvaluatedKey
    })
  );

  items.push(...(response.Items ?? []));
  lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);

console.log(`Found ${items.length} songs on the album`);

The cursor is three attributes wide, not two

Run the loop above against 300 songs on one album and inspect the LastEvaluatedKey it hands back:

table query  -> ['Artist', 'SongTitle']
GSI query    -> ['AlbumTitle', 'Artist', 'SongTitle']

A GSI key is not unique, so the index key alone cannot resume a scan. DynamoDB returns the index key and the base-table key together, and both must go back in ExclusiveStartKey untouched. This is why a hand-rolled cursor that stores "the last sort key I saw" works on a table and quietly loses or repeats items on an index — and why persisting that key to a client is a bad idea when the table key is a user id you would rather not leak.

ConsistentRead: true is a 400, not an upgrade

The instinct is that a strongly consistent read costs more capacity and you get fresher data. On a GSI it costs you the request:

ValidationException: Consistent reads are not supported on global secondary indexes
HTTP 400

The API reference is equally blunt: "Strongly consistent reads are not supported on global secondary indexes. If you query a global secondary index with ConsistentRead set to true, you will receive a ValidationException." Scan on a GSI rejects the same flag with the same message. Local secondary indexes do accept it.

Reproduced 2026-07-28 against DynamoDB Local (amazon/dynamodb-local) with @aws-sdk/client-dynamodb 3.1095.0 on node v24.18.0. The error text and key shapes are the engine's own output.

Explanation

  • IndexName does not replace TableName. Both go in the same command, and the KeyConditionExpression then names the index's partition key (AlbumTitle), not the table's, with the same operator set as a table query.
  • You get the projection and nothing else. A GSI query returns what the index projects (ALL, KEYS_ONLY, or the INCLUDE list), and per the API reference "global secondary index queries cannot fetch attributes from the parent table". A missing attribute means a follow-up GetItem on the base key, or a wider projection and a re-created index.
  • Items missing the index key never appear. That is the sparse-index pattern, and it is a feature: index only the rows with status = "OPEN" and the GSI stays small. It is also the reason a GSI query can return fewer items than you expect and raise no error.
  • Replication is asynchronous, so a write that just landed on the table may not be in the index yet. Budget for that in read-after-write paths rather than retrying in a tight loop.

Do it visually

Adding a GSI after the fact is the expensive way to learn this. The single-table design planner takes your access patterns and works out which of them need an index key and which the base table already serves.

To browse a table's indexes and run GSI queries from a form, with a paginated grid, download DynoTable.

References

Build this request visually

Compose this operation in the free DynamoDB Query Builder — key condition, filter, index, Limit, sort order, and a pagination loop — and copy it back as a runnable SDK v3, CLI, or boto3 program.

Open the DynamoDB Query Builder

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.