DynamoDB FilterExpression: Syntax, Examples and Read Costs

Written by A. Yohan Malshika
Last updated: October 23rd, 2026
A DynamoDB FilterExpression removes nonmatching items from a Query or Scan response after DynamoDB reads them. It reduces the data returned to your application, but does not reduce consumed read capacity. Both the request's Limit and the 1 MB read-page boundary apply before the filter, so a response can contain no items and still have another page.
How a filter expression works
A filtered read has three stages:
- DynamoDB reads candidates. Query uses a partition key condition and an optional sort key condition; Scan evaluates items across the selected table or index.
- DynamoDB tests the filter against the candidates in that page.
- It returns matching items, counts and, when applicable, a continuation key.
Reading 100 KB and returning 10 KB after filtering still charges for the read work on the original candidates. The 1 MB boundary applies to each Query or Scan request before filtering, not to all DynamoDB operations or to the total result set. Follow the continuation key to read more.
A Query filter cannot contain the partition key or sort key of the queried table or index. Put those conditions in KeyConditionExpression. A Scan has no key condition and can filter on key attributes, but such a filter does not turn it into a targeted lookup. See Query vs Scan for choosing the access operation and the Scan guide for full-table jobs. AWS documents the processing order in its Query filter reference.
Comparison operators and logical syntax
Use attribute paths on one side and placeholders for values on the other. The following expressions assume #count aliases employee_count and each colon-prefixed placeholder is defined in ExpressionAttributeValues.
| Operator | Example | Match |
|---|---|---|
= | #count = :n | Equal |
<> | #count <> :n | Not equal |
< | #count < :n | Less than |
<= | #count <= :n | Less than or equal |
> | #count > :n | Greater than |
>= | #count >= :n | Greater than or equal |
BETWEEN | #count BETWEEN :low AND :high | Inclusive range |
IN | #status IN (:active, :paused) | Equal to an entry in a list of values |
AND | #count >= :n AND #status = :active | Both conditions |
OR | #status = :active OR #status = :paused | Either condition |
NOT | NOT contains(#tags, :tag) | Inverse of a condition |
IN accepts up to 100 values. It compares an attribute to multiple alternatives; contains checks whether a collection contains an element. They solve different problems. Comparisons are type-sensitive: the Number 10 and the String "10" are different values. Store counts as Numbers if you need numeric comparisons.
Use parentheses to express mixed logic explicitly:
Without the parentheses, AND takes precedence over OR. DynamoDB uses <> for not equal, not JavaScript's != or !==. It does not offer SQL LIKE or a regular-expression filter.
Filter functions
Function names are case-sensitive. The full grammar and supported types are in the AWS expression reference.
| Function | Example | Purpose |
|---|---|---|
attribute_exists(path) | attribute_exists(#owner) | Attribute is present, including a stored NULL |
attribute_not_exists(path) | attribute_not_exists(#archivedAt) | Attribute is absent |
attribute_type(path, type) | attribute_type(#count, :numberType) | Attribute has the requested DynamoDB type |
begins_with(path, prefix) | begins_with(#name, :prefix) | Value starts with the supplied prefix |
contains(path, value) | contains(#tags, :tag) | String contains a substring, or list/set contains an element |
size(path) | size(#members) >= :minimum | Compare an attribute's size to a number |
For attribute_type, supply the type name as a string value, for example ":numberType": "N" with the document client. Supported type names are S, N, B, BOOL, NULL, M, L, SS, NS and BS.
size gives string length, binary length in bytes, the number of set elements, or the number of immediate children in a list or map. It does not measure the billable size of an entire item. contains on a string is case-sensitive; normalize a separate attribute at write time if the access pattern needs case-insensitive matching.
A missing attribute and a stored NULL are different. Use attribute_not_exists(#owner) for missing data and attribute_type(#owner, :nullType) with :nullType set to "NULL" for an explicit null.
FilterExpression examples with AWS SDK for JavaScript v3
These examples use a table named projects-manager, with String partition key PK and String sort key SK. Project items have attributes such as name, projectId, employee_count, status and tags. Install @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb, configure an AWS Region and credentials, then run the code as an ES module.
Not equal: exclude one project name
The key condition selects the organization. The filter then excludes items named Project X within that organization's read page. This is one request, not a loop over every result.
The equivalent Scan request below is valid, but evaluates table items before checking the organization. Reuse client from the preceding example:
Use the Query version when the organization key is known. Scan does not accept KeyConditionExpression.
Contains: match a substring or a list element
For a tags list such as ["urgent", "internal"], change the expression to contains(#tags, :tag), the alias to "#tags": "tags", and the value to ":tag": "urgent". Supply the element itself, not a one-element list or set.
Multiple filters: combine contains and equality
If looking up projectId is a frequent operation, give it a key-based access pattern instead of repeatedly scanning and filtering.
Counts and Boolean values
This includes a count of exactly 10. Change >= to > for strictly more than 10. The Boolean value is true, not the string "true". A Boolean attribute by itself is not a complete filter condition; compare it to a Boolean placeholder.
Nested attributes, lists and special names
Use dot notation for maps and zero-based indexes for list positions. For an item with details.owner.email and a members list of maps:
This checks the first member's role, not every member. Filter expressions do not have a wildcard path such as members[*].role. For arbitrary list-element properties, reshape the data into a searchable attribute or process the returned list in your application.
Alias each path segment separately. "#email": "details.owner.email" means a single literal attribute name containing dots, not three nested attributes. Aliases also handle reserved words, spaces and hyphens. The AWS alias documentation explains that distinction. Dynobase's JSON path filtering can build nested filters from values you inspect in an item.
Limit, Count and empty pages
Suppose Limit is 25 and only two of the first 25 evaluated items pass the filter. The response can have ScannedCount: 25, Count: 2 and a LastEvaluatedKey. If none passes, Items can be empty with the same continuation requirement.
ScannedCountcounts candidates evaluated in this request.Countcounts items remaining after its filter.LastEvaluatedKeyidentifies where to resume; it does not promise another matching item.
Continue until the key is absent or empty. Stopping when Items.length is zero silently loses later matches. The pagination guide includes manual loops, SDK paginators and API cursor handling.
When to use filters, and when to change the model
Filters fit optional refinements on a small, well-targeted item collection: hiding archived projects, selecting a Boolean flag, or excluding a few records. They reduce network payload and keep simple predicate logic out of the application. ProjectionExpression further reduces returned attributes, but neither mechanism reduces read capacity for the same candidates.
A filter is a poor substitute for a key or index when most evaluated items are discarded. Inspect Count, ScannedCount and ConsumedCapacity across the whole request, then decide whether the predicate deserves an index or a different key structure. Do not interpret a low returned count as a cheap read.
Troubleshooting a filter that returns nothing
Check the stored attribute type and spelling first, including nested paths and capitalization. Then check that every #alias and :value has a matching entry, and remove unused entries when changing the expression. With the document client, use plain JavaScript values; the low-level client and AWS CLI require DynamoDB JSON type wrappers. Finally, inspect LastEvaluatedKey before deciding there are no matches.
Frequently Asked Questions
Does FilterExpression reduce DynamoDB read cost?
Can I filter a partition key or sort key?
Why does Limit return fewer items than requested?
How do I filter a nested attribute?
#details.#owner.#email. List positions use zero-based brackets, such as #members[0].What is the difference between contains and IN?
contains tests a substring or a member of a list or set. IN compares one attribute value against a list of alternative values.Dynobase is a Professional GUI Client for DynamoDB
Start your 7-day free trial today
Product Features
DynamoDB Tools
- SSH GUI Client
- Chat With Cloud
- MongoDB GUI Client
- AWS Cost Monitoring
- DynamoDB Query Builder
- DynamoDB Code Examples
- DynamoDB Node.js Queries
- DynamoDB JSON Converter
- DynamoDB UUID Generator
- DynamoDB Local Admin GUI
- DynamoDB Pricing Calculator
- DynamoDB Single-Table Design
- Best DynamoDB GUI Clients
- Dynobase vs DynoTable