boto/boto3 · error · DynamoDBNeedsKeyConditionError
Attribute object {value.name} is of type {type(value)}. KeyC
Error message
Attribute object {value.name} is of type {type(value)}. KeyConditionExpression only supports Attribute objects of type Key What it means
Raised in _build_expression_component when is_key_condition is True and an AttributeBase operand is an Attr (not a Key). DynamoDB's KeyConditionExpression may only reference partition/sort key attributes represented by Key(...); using Attr(...) (which denotes a non-key item attribute) is semantically invalid for key conditions. The builder explicitly checks isinstance(value, Key) and rejects Attr objects.
Source
Thrown at boto3/dynamodb/conditions.py:407
attribute_value_placeholders,
has_grouped_values,
is_key_condition,
):
# Continue to recurse if the value is a ConditionBase in order
# to extract out all parts of the expression.
if isinstance(value, ConditionBase):
return self._build_expression(
value,
attribute_name_placeholders,
attribute_value_placeholders,
is_key_condition,
)
# If it is not a ConditionBase, we can recurse no further.
# So we check if it is an attribute and add placeholders for
# its name
elif isinstance(value, AttributeBase):
if is_key_condition and not isinstance(value, Key):
raise DynamoDBNeedsKeyConditionError(
f'Attribute object {value.name} is of type {type(value)}. '
f'KeyConditionExpression only supports Attribute objects '
f'of type Key'
)
return self._build_name_placeholder(
value, attribute_name_placeholders
)
# If it is anything else, we treat it as a value and thus placeholders
# are needed for the value.
else:
return self._build_value_placeholder(
value, attribute_value_placeholders, has_grouped_values
)
def _build_name_placeholder(self, value, attribute_name_placeholders):
attribute_name = value.name
# Figure out which parts of the attribute name that needs replacement.
attribute_name_parts = ATTR_NAME_REGEX.findall(attribute_name)View on GitHub (pinned to c7b4afac23)
Solutions
- Use Key(...) for every attribute in a KeyConditionExpression: Key('pk').eq('v').
- For sort-key range conditions also use Key: Key('pk').eq('v') & Key('sk').begins_with('s').
- Reserve Attr(...) exclusively for FilterExpression / ConditionExpression (non-key filters).
Example fix
# before
table.query(KeyConditionExpression=Attr('pk').eq('user#1'))
# after
table.query(KeyConditionExpression=Key('pk').eq('user#1')) Defensive patterns
Strategy: type-guard
Validate before calling
from boto3.dynamodb.conditions import Key, AttributeBase
def validate_key_condition(expr):
# crude check: ensure no Attr leaks in by scanning repr
if 'Attr' in repr(expr):
raise TypeError('KeyConditionExpression must use Key(), not Attr()')
return expr Type guard
from boto3.dynamodb.conditions import Key
def is_key(obj) -> bool:
return isinstance(obj, Key) Try / catch
from boto3.exceptions import DynamoDBNeedsKeyConditionError
try:
table.query(KeyConditionExpression=kce)
except DynamoDBNeedsKeyConditionError:
# rebuild using Key() instead of Attr()
table.query(KeyConditionExpression=Key('pk').eq('v')) Prevention
- Convention: Key() for key-conditions, Attr() for filter/condition expressions only.
- Review any KeyConditionExpression that was adapted from a FilterExpression.
- Add a lint rule that flags Attr( inside a query() call.
When it happens
Trigger: table.query(KeyConditionExpression=Attr('pk').eq('v')) — used Attr instead of Key. Also Key('pk').eq('v') & Attr('sk').begins_with('s') where the sort-key side is an Attr.
Common situations: Copy-pasting a FilterExpression (built with Attr) into KeyConditionExpression; not knowing that Key vs Attr is enforced positionally; renaming attributes during a schema migration.
Related errors
- Expecting a ConditionBase object. Got {value} of type {type(
- AND operation cannot be applied to value {value} of type {ty
- OR operation cannot be applied to value {value} of type {typ
- NOT operation cannot be applied to value {value} of type {ty
- Value must be of the following types: {types}
AI-assisted analysis of boto/boto3@c7b4afac23 (2026-08-04).
Data as JSON: /data/errors/be1e55cea141d9fe.json.
Report an issue: GitHub.