DynamoDB Query with the AWS CLI
aws dynamodb query reads one partition, optionally narrowed by the sort key (Query vs Scan covers when that is the right call, and key condition expressions lists every legal operator). What the CLI adds on top is a pagination layer of its own, and it is the source of most surprises on this command.
Code
aws dynamodb query \
--table-name 'Music' \
--key-condition-expression '#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)' \
--expression-attribute-names '{"#hashKey":"Artist","#rangeKey":"SongTitle"}' \
--expression-attribute-values '{":hashKeyValue":{"S":"Arturo Sandoval"},":rangeKeyValue":{"S":"C"}}'The #hashKey/#rangeKey aliases resolve to Artist/SongTitle through --expression-attribute-names, which is what keeps a reserved word from breaking the command. Add --no-scan-index-forward for descending sort-key order; ascending is the default.
Pagination
By default the CLI auto-paginates — it follows LastEvaluatedKey internally and prints the combined result. To page manually (e.g. for large result sets), control it with:
aws dynamodb query \
--table-name 'Music' \
--key-condition-expression '#hashKey = :hashKeyValue' \
--expression-attribute-names '{"#hashKey":"Artist"}' \
--expression-attribute-values '{":hashKeyValue":{"S":"Arturo Sandoval"}}' \
--page-size 100 \
--max-items 50
# The output includes a "NextToken"; pass it back with --starting-token to continue.Explanation
The CLI hides the pagination, including from the cost figure. Seeded a partition with 30 items of ~60 KB each, about 1.8 MB and therefore two service pages, then ran the same query three ways with --return-consumed-capacity TOTAL:
default (auto-paginate) Count: 30 CapacityUnits: 132.0 LastEvaluatedKey: null
--no-paginate Count: 18 CapacityUnits: 132.0 LastEvaluatedKey: {…S017}
--max-items 3 Count: 18 items printed: 3 NextToken: eyJFeGNsdXNpdmVTdGFydEtleSI6…Paging by hand showed the true cost: page 1 was 18 items at 132.0 units, page 2 was 12 items at 88.0, so the query really consumed 220.0 read units. The auto-paginated run made both calls, returned all 30 items, and reported 132.0. The CLI merges Items and Count across pages but not ConsumedCapacity, so the printed number understates this query by 40%. If you are sizing capacity from CLI output, page manually or you will size for one page.
--max-items is a printing limit. It is not a Limit. The third run above printed three items and still reported Count: 18 and ScannedCount: 18, because the service page it truncated was 18 items and roughly 1 MB. You paid for all of it. The DynamoDB parameter that genuinely bounds the read is Limit, and the CLI exposes it as --page-size.
So the two flags do unrelated jobs. --page-size becomes the API's Limit and changes what each service call reads; --max-items only decides how much of the merged result reaches your terminal, and emits a NextToken for the rest. That token is a base64 blob of the CLI's own bookkeeping, not DynamoDB's LastEvaluatedKey, and it goes back in through --starting-token.
There is no --limit and no --exclusive-start-key. Check aws dynamodb query help on 2.36.9 and neither appears in the synopsis: the CLI removes both of DynamoDB's pagination parameters and substitutes its own three. So the natural loop, take LastEvaluatedKey from one call and feed it to the next, has no flag to feed it to. The way back to the raw API is --cli-input-json, which takes the request verbatim:
--cli-input-json with "Limit": 5 and an "ExclusiveStartKey"
→ Count: 5 CapacityUnits: 37.0 LastEvaluatedKey: {"Artist":…,"SongTitle":"S007"}Note that this also switched the paginator off: the run returned one page and a real LastEvaluatedKey even without --no-paginate. If you are writing a shell loop over a large partition, --cli-input-json is the honest form, and --no-paginate is the quick one.
--query runs after the money is spent. The global --query flag is JMESPath applied to the response in your shell. A JMESPath expression such as Items[?Year > '2010'] looks like a filter and is not: every item was read, transferred and billed before JMESPath saw it. --filter-expression at least stops the data being transferred, but AWS is explicit that it "is applied after the items have already been read; the process of filtering does not consume any additional read capacity units" (fetched 2026-07-28). That cuts both ways, since it means the filter does not reduce them either. The only way to read less is a narrower key condition or an index.
A page is 1 MB, regardless of what you asked for. "A single Query operation will read up to the maximum number of items set (if using the Limit parameter) or a maximum of 1 MB of data" (fetched 2026-07-28). A partition wider than that always paginates, which is why the 30-item query above was never one call.
Querying an index takes one more flag. --index-name switches the key condition to that index's keys; a global secondary index also rejects --consistent-read. See Query a GSI with the AWS CLI.
Do it visually
Getting the key condition, the two placeholder maps and the pagination loop right in one command is the whole difficulty here. The free DynamoDB Query Builder composes the request, including the index and the paging, and emits it as a runnable CLI command.
To run queries against your own tables — key-condition form, a grid that pages as you scroll, copy the request back out as a CLI command — download DynoTable.
Related guides
- Query vs. Scan — why
queryis the right default. - Pagination —
LastEvaluatedKey,ExclusiveStartKey, and whyLimitis not a page size. - "Query condition missed key schema element" — the key condition names the wrong attribute or skips the partition key.
- "Query key condition not supported" — an operator the key condition can't use, like contains or a second sort-key condition.
References
- Query — Amazon DynamoDB API Reference
- query — AWS CLI Command Reference
- Using the pagination options in the AWS CLI — AWS CLI User Guide
- Filtering AWS CLI output — AWS CLI User Guide
- Querying tables — Amazon DynamoDB Developer Guide
Measured 2026-07-28 with aws-cli/2.36.9 against DynamoDB Local (amazon/dynamodb-local) on port 9000, over a partition of 30 items at ~60 KB each. The counts, tokens and capacity readings above are captured output. DynamoDB Local computes capacity with the documented rounding rules; treat the absolute figures as a demonstration of the shape, and measure your own tables against the service before sizing.