Describe the feature you'd like to request
Support for DynamoDB's full expression capabilities in generated GraphQL mutations:
-
Condition expressions - The GraphQL mutations already accept condition: Model*ConditionInput, but this only covers basic field comparisons. Advanced conditions like attribute_not_exists(), combined with update logic, aren't fully supported.
-
Update expressions - Currently all updates use simple SET field = :value. DynamoDB supports much richer update semantics:
ADD for atomic counter increment/decrement and set operations
SET field = field + :value for relative updates
SET field = if_not_exists(field, :default) for initialization
SET field = list_append(field, :items) for atomic list operations
REMOVE field conditionally
These patterns are essential for concurrent-safe operations like counters, rate limiting, inventory management, and state machines.
Describe the solution you'd like
Option A: Dedicated counter/atomic field types (recommended for common cases)
An opt-in field modifier for the most common use case - atomic counters:
const schema = a.schema({
Post: a.model({
id: a.id(),
title: a.string(),
viewCount: a.integer().counter(), // or a.counter()
})
});
Generates:
input UpdatePostInput {
id: ID!
title: String
viewCount: Int
viewCount_increment: Int # Atomic ADD operation
}
Option B: Expression-level escape hatch (for advanced cases)
For complex expressions that don't fit a predefined pattern, allow passing update expression components:
mutation {
updatePost(
input: { id: "123" }
updateExpression: {
set: [{ field: "viewCount", expression: "if_not_exists(viewCount, :zero) + :inc" }]
expressionValues: { ":zero": 0, ":inc": 1 }
}
condition: { viewCount: { lt: 100 } }
) {
id
viewCount
}
}
Option C: Extend condition input for attribute functions
Expand Model*ConditionInput to support:
attributeNotExists (exists today but scoped to field)
- Combining conditions with update operations atomically
Describe alternatives you've considered
- Custom VTL/JS resolvers - Works but defeats the purpose of generated APIs
- Lambda via
@function - Adds latency and infrastructure overhead
- Direct DynamoDB SDK - Bypasses Amplify entirely, loses type safety and integrated auth
Additional context
Real-world example - rate limiting with atomic counter:
// What we have to do today (bypassing Amplify):
await docClient.send(new UpdateCommand({
TableName: tableName,
Key: { id },
UpdateExpression: "SET activeCount = if_not_exists(activeCount, :zero) + :inc, updatedAt = :now",
ConditionExpression: "attribute_not_exists(activeCount) OR activeCount < :limit",
ExpressionAttributeValues: {
":zero": 0,
":inc": 1,
":limit": 10,
":now": new Date().toISOString(),
},
}));
Implementation note: The VTL resolver generation in packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/mutation.ts already initializes an expAdd bucket (line 50) and has logic to build ADD expressions (lines 100-109), but nothing populates it. Some infrastructure already exists.
Generated client note: The generated TypeScript client (@aws-amplify/data-schema) also doesn't expose the existing condition parameter in update() and delete() methods, even though the underlying GraphQL mutations support it. Full end-to-end support would require changes in both this repo (for update expressions) and amplify-data (for exposing conditions in the client API).
Describe the feature you'd like to request
Support for DynamoDB's full expression capabilities in generated GraphQL mutations:
Condition expressions - The GraphQL mutations already accept
condition: Model*ConditionInput, but this only covers basic field comparisons. Advanced conditions likeattribute_not_exists(), combined with update logic, aren't fully supported.Update expressions - Currently all updates use simple
SET field = :value. DynamoDB supports much richer update semantics:ADDfor atomic counter increment/decrement and set operationsSET field = field + :valuefor relative updatesSET field = if_not_exists(field, :default)for initializationSET field = list_append(field, :items)for atomic list operationsREMOVE fieldconditionallyThese patterns are essential for concurrent-safe operations like counters, rate limiting, inventory management, and state machines.
Describe the solution you'd like
Option A: Dedicated counter/atomic field types (recommended for common cases)
An opt-in field modifier for the most common use case - atomic counters:
Generates:
Option B: Expression-level escape hatch (for advanced cases)
For complex expressions that don't fit a predefined pattern, allow passing update expression components:
Option C: Extend condition input for attribute functions
Expand
Model*ConditionInputto support:attributeNotExists(exists today but scoped to field)Describe alternatives you've considered
@function- Adds latency and infrastructure overheadAdditional context
Real-world example - rate limiting with atomic counter:
Implementation note: The VTL resolver generation in
packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/mutation.tsalready initializes anexpAddbucket (line 50) and has logic to build ADD expressions (lines 100-109), but nothing populates it. Some infrastructure already exists.Generated client note: The generated TypeScript client (
@aws-amplify/data-schema) also doesn't expose the existingconditionparameter inupdate()anddelete()methods, even though the underlying GraphQL mutations support it. Full end-to-end support would require changes in both this repo (for update expressions) and amplify-data (for exposing conditions in the client API).