boto/boto3 · error · DynamoDBNeedsKeyConditionError
Attribute object is of type . KeyConditionExpression only…
Error message
Attribute object {value.name} is of type {type(value)}. KeyConditionExpression only supports Attribute objects of type Key What it means
KeyConditionExpression may only reference Key attributes (the partition and sort keys). During expression building, if is_key_condition is True and the value is an AttributeBase that is not a Key (i.e. an Attr), boto3 raises DynamoDBNeedsKeyConditionError. This enforces the DynamoDB rule that only key attributes may appear in KeyConditionExpression; non-key attributes must go in FilterExpression.
Solutions
- Use Key(name) for everything in KeyConditionExpression; move non-key predicates to FilterExpression with Attr(name).
- Check that every attribute appearing in your key condition was created with Key(), not Attr().
- When combining conditions for the key expression, ensure both operands descend from Key comparisons.
Example fix
# before
table.query(
KeyConditionExpression=Key('pk').eq('u1') & Attr('created').gt(0),
)
# after
table.query(
KeyConditionExpression=Key('pk').eq('u1') & Key('sk').gt('2024'),
FilterExpression=Attr('created').gt(0),
) Defensive patterns
Strategy: type-guard
Validate before calling
from boto3.dynamodb.conditions import Key, ConditionBase
# Walk the AST of the condition and ensure all leaves are Key, not Attr
def only_keys(c):
if isinstance(c, ConditionBase):
return all(only_keys(v) for v in c._values)
return isinstance(c, Key) Type guard
from boto3.dynamodb.conditions import Key, AttributeBase
def is_key_attr(v) -> bool:
return isinstance(v, Key) or not isinstance(v, AttributeBase) Try / catch
from boto3.exceptions import DynamoDBNeedsKeyConditionError
try:
table.query(KeyConditionExpression=kc)
except DynamoDBNeedsKeyConditionError:
# move the Attr-based predicate into FilterExpression
table.query(KeyConditionExpression=kc_key_only, FilterExpression=attr_filter) Prevention
- Reserve Key() for KeyConditionExpression and Attr() for FilterExpression.
- Do not combine Key conditions with Attr conditions in the key expression.
- Document which attributes are keys when building queries dynamically.
When it happens
Trigger: Passing Attr('non_key_col').eq(...) inside KeyConditionExpression; building a KeyCondition from an Attr object by mistake; combining a Key condition with an Attr condition via & and putting the result in key_condition_expression.
Common situations: Confusing Attr (for filter_expression) with Key (for key_condition_expression); trying to filter on a non-keyed attribute inside the key condition to 'save' a filter step.
Related errors
- AND operation cannot be applied to value
- Expecting a ConditionBase object. Got
- NOT operation cannot be applied to value
- OR operation cannot be applied to value
- Dynamodb type is not supported
AI-assisted analysis of boto/boto3@6e10b029c1 (2026-08-11).
Data as JSON: /api/errors/be1e55cea141d9fe.
Report an issue: GitHub.
Appendix: 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 6e10b029c1)