DynamoDB Expressions: Filter, Condition, Projection & Update

DynamoDB expressions explained — key condition, filter, condition, projection and update expressions, plus expression attribute names and values.

5 min read· Updated Jun 15, 2026
On this page

DynamoDB expressions are the strings you pass to API calls to describe what to match, what to return, what to change, and when to allow a write. There are five kinds, each with a distinct job, and they all rely on placeholder syntax for attribute names and values. Getting them right is most of the work in using the low-level API well.

The five expression types

ExpressionUsed inPurpose
KeyConditionExpressionQuerySelect items by primary key
FilterExpressionQuery, ScanDrop items after they’re read
ProjectionExpressionreadsReturn only certain attributes
ConditionExpressionPutItem, UpdateItem, DeleteItemAllow the write only if true
UpdateExpressionUpdateItemDescribe how to modify the item

Keep their roles straight and most confusion disappears: key conditions and filters read, conditions guard writes, updates mutate, projections trim output.

Placeholders: names and values

Two helpers appear in almost every expression. Expression attribute names (#x) substitute for attribute names, and expression attribute values (:y) substitute for the values you compare against.

You must use a name placeholder when the attribute is a reserved word (name, status, size, count, and ~570 others) or contains special characters. Values are always placeholders — you never inline a literal into the expression string.

resp = table.query(
    KeyConditionExpression="#pk = :pk AND begins_with(SK, :pre)",
    FilterExpression="#s = :st",
    ExpressionAttributeNames={"#pk": "PK", "#s": "status"},
    ExpressionAttributeValues={
        ":pk": "CUSTOMER#42",
        ":pre": "ORDER#",
        ":st": "SHIPPED",
    },
)

Here status is a reserved word, so it goes through #s. (High-level SDKs like boto3’s Key/Attr and the JS DocumentClient generate these placeholders for you.)

Key condition expressions

A KeyConditionExpression is the only way to target items in a Query. It must include an equality on the partition key and may add a sort-key condition:

  • Partition key: equality only (PK = :pk)
  • Sort key: =, <, <=, >, >=, BETWEEN, or begins_with(...)
#pk = :pk AND SK BETWEEN :start AND :end

This is what makes a Query cheap — DynamoDB uses it to seek directly into a partition. You can’t reference non-key attributes here; that’s what filters are for. See query vs scan for why key conditions matter so much.

Filter expressions

A FilterExpression removes items from the result after they’ve been read from the table. The critical consequence: filters don’t save read capacity. You’re billed for every item the key condition (or Scan) examined, and the 1 MB page limit applies before filtering.

#s = :st AND total > :min

Use filters to trim small result sets you’ve already paid to read, not to substitute for a missing index. Filters support =, <>, comparisons, BETWEEN, IN, and functions like attribute_exists, attribute_not_exists, contains, and begins_with.

Projection expressions

A ProjectionExpression limits which attributes come back, reducing payload size (though not read capacity, which is based on item size read). List the attributes, using #name placeholders for reserved words and dotted paths for nested data:

ProjectionExpression="#n, email, address.city, orders[0]"
ExpressionAttributeNames={"#n": "name"}

This is handy when items are large but a list view needs only a few fields.

Condition expressions

A ConditionExpression makes a write conditional. The PutItem, UpdateItem, or DeleteItem succeeds only if the condition is true; otherwise it fails with ConditionalCheckFailedException and the item is untouched. This is how you get optimistic concurrency and “create only if absent” semantics without a read-then-write race.

# Insert only if the item doesn't already exist
table.put_item(
    Item={"PK": "USER#1", "SK": "PROFILE", "email": "a@b.com"},
    ConditionExpression="attribute_not_exists(PK)",
)

# Optimistic locking on a version attribute
table.update_item(
    Key={"PK": "USER#1", "SK": "PROFILE"},
    UpdateExpression="SET email = :e, version = :nv",
    ConditionExpression="version = :cv",
    ExpressionAttributeValues={":e": "c@d.com", ":cv": 7, ":nv": 8},
)

attribute_not_exists(PK) is the idiomatic “create if not exists” — if the partition key is absent, the item is new. Conditions are evaluated atomically on the server, so two concurrent writers can’t both win.

Update expressions

An UpdateExpression describes how to mutate an item in place, using four clauses:

ClauseDoesExample
SETAssign / arithmeticSET email = :e, total = total + :d
REMOVEDelete attributesREMOVE temp, draft
ADDAtomic number / set addADD views :inc
DELETERemove from a setDELETE tags :t
table.update_item(
    Key={"PK": "POST#9", "SK": "META"},
    UpdateExpression="SET title = :t ADD views :one REMOVE draft",
    ExpressionAttributeValues={":t": "Hello", ":one": 1},
)

ADD views :one is an atomic counter — DynamoDB applies the increment server-side, so concurrent increments never clobber each other. SET also supports functions like if_not_exists(attr, :default) and list_append(list, :items) for safe initialization and appends. By default UpdateItem creates the item if it doesn’t exist (an upsert); add a condition if you don’t want that.

A note on PartiQL

If the placeholder syntax feels heavy, PartiQL lets you express many of these operations in SQL-like statements (SELECT … WHERE, UPDATE … SET). It’s the same engine underneath — a SELECT with a key predicate becomes a Query, and one without becomes a Scan — so the cost rules above still apply. Expressions give you finer control and access to every feature; PartiQL trades some of that for familiarity.

Common pitfalls

  • Reserved words silently break expressions with a validation error. When in doubt, use a #name placeholder.
  • Confusing filter with key condition. A filter never reduces what you read — only a key condition or index does.
  • Forgetting conditions on writes. Without a ConditionExpression, concurrent updates can overwrite each other; add a version check for optimistic locking.
  • Inlining values into the expression string. Always bind through ExpressionAttributeValues.

Try them against real data

Hand-building ExpressionAttributeNames and ExpressionAttributeValues maps is error-prone, and a typo surfaces as an opaque validation error. Tablyne, a native DynamoDB GUI, builds these expressions for you and shows the exact request it sends, which is a fast way to learn the syntax and confirm a condition or update does what you intended before wiring it into code.

Master the five expression types and their placeholders and you’ve covered nearly the entire DynamoDB read-and-write surface — everything else is data modeling on top of it.

Frequently asked questions

When do I need ExpressionAttributeNames?

Whenever an attribute name is a DynamoDB reserved word (like name, status, or size) or contains characters such as dots or spaces. You substitute a placeholder like #s and map it in ExpressionAttributeNames to avoid a validation error.

What's the difference between a filter expression and a condition expression?

A FilterExpression trims items from a Query or Scan result after they're read, so it doesn't save read capacity. A ConditionExpression gates a write — the PutItem, UpdateItem, or DeleteItem only succeeds if the condition is true.

How do I do an atomic counter in DynamoDB?

Use an UpdateExpression with the ADD action or SET with arithmetic, like SET views = views + :inc. DynamoDB applies it atomically on the server, so concurrent increments don't lose updates.

Keep reading