DynamoDB UpdateItem in Go (AWS SDK v2)
UpdateItem is straightforward in Go; the awkward part is that map[string]types.AttributeValue is a map of interfaces, so both the values you send and the ones you get back are pointers to one of nine member structs. That single design choice explains most of the friction below, starting with the fact that the fmt.Println in this snippet does not print your item.
Code
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
out, err := client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
TableName: aws.String("Music"),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
"SongTitle": &types.AttributeValueMemberS{Value: "Cubano Chant"},
},
UpdateExpression: aws.String("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2"),
ExpressionAttributeNames: map[string]string{
"#upd0": "Genre",
"#upd1": "Year",
"#upd2": "Awards",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":updValue0": &types.AttributeValueMemberS{Value: "Latin Jazz"},
":updValue1": &types.AttributeValueMemberN{Value: "1994"},
":updValue2": &types.AttributeValueMemberN{Value: "1"},
},
ReturnValues: types.ReturnValueAllNew,
})
if err != nil {
log.Fatalf("update item: %v", err)
}
fmt.Println(out.Attributes) // the item after the update
}Explanation
AttributeValueMemberN.Valueis astring, not a numeric type, andattributevalue.Marshalkeeps it that way:int64(9007199254740993)marshals to"9007199254740993"exactly. DynamoDB numbers carry 38 digits of precision, more than any Go float, so the SDK never converts. You do the parsing, at the edge, deliberately.ReturnValues: types.ReturnValueAllNew— the constant's value is the literal"ALL_NEW".types.ReturnValueAllNew.Values()lists all five (NONE,ALL_OLD,UPDATED_OLD,ALL_NEW,UPDATED_NEW) if you would rather read them off the type than the docs.UpdateExpressionis an*stringyou build yourself, or hand off to theexpressionpackage below. Either way DynamoDB is the only parser:ADD #upd2 :updValue2gives the atomic increment,attribute_exists(Artist)in aConditionExpressionmakes the call update-only instead of an upsert, and the clause grammar lives in update expressions.ValidationExceptionhas no Go type.types/errors.godefines 35 error structs, includingConditionalCheckFailedException,TransactionCanceledException,ProvisionedThroughputExceededExceptionandTransactionConflictException. Validation failures are not among them, so a bad expression surfaces as a genericsmithy.APIErroryou can only recognize by string:operation error DynamoDB: UpdateItem, https response error StatusCode: 400, RequestID: 702d67f1-4f15-43dc-b3e9-cea691878801, api error ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: YearNote the
api errorprefix, which the modeledConditionalCheckFailedExceptionbelow does not get. Its presence is a decent signal thaterrors.Asagainst a concrete type is not going to help you here.A failed condition, by contrast, is a real type.
var ccf *types.ConditionalCheckFailedException; errors.As(err, &ccf)matches, and withReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOldon the input,ccf.Itemarrives populated with the item as it actually was.ccf.ErrorMessage()is the bareThe conditional request failed, without the transport preamble thaterr.Error()prepends.
Reading the result back
fmt.Println(out.Attributes) on an interface map prints addresses:
map[Artist:0x1c8cf589e060 Awards:0x1c8cf589e078 Genre:0x1c8cf589e090 SongTitle:0x1c8cf589e0c0 Year:0x1c8cf589e0a8]Two ways out. Unmarshal into a struct, which is what most code should do:
var song struct {
Artist string
Genre string
Year int
Awards int
}
err = attributevalue.UnmarshalMap(out.Attributes, &song)
// {Artist:Arturo Sandoval Genre:Latin Jazz Year:1994 Awards:1}Or assert the one member you care about, and remember .Value is a string:
n, ok := out.Attributes["Awards"].(*types.AttributeValueMemberN)
if ok {
awards, _ := strconv.Atoi(n.Value) // "1" -> 1
fmt.Println(awards)
}Let the expression package write it
Go is the only SDK on this site that will generate the UpdateExpression for you. feature/dynamodb/expression composes the clauses and both maps:
upd := expression.Set(expression.Name("Genre"), expression.Value("Latin Jazz")).
Set(expression.Name("Year"), expression.Value(1994)).
Add(expression.Name("Awards"), expression.Value(1))
expr, _ := expression.NewBuilder().WithUpdate(upd).Build()What comes out is not what you wrote:
UpdateExpression: ADD #0 :0
SET #1 = :1, #2 = :2
Names: map[#0:Awards #1:Genre #2:Year]The builder reordered the clauses, separated them with a newline, and numbered the placeholders itself, so #0 is Awards rather than the first name you mentioned. Nothing downstream cares, but the strings are not stable across edits, which makes them a poor thing to assert on in tests. Pass expr.Update(), expr.Names() and expr.Values() straight into UpdateItemInput and never look at them.
The upside is that it aliases every name, so reserved words stop being a class of bug you can ship. If you hand-write the expression instead, run the attribute names through the DynamoDB reserved words checker first — the AWS list has 573 entries and Year, Name and Status are all on it. And if you would rather look at an item as data than as a map of interface pointers, download DynoTable.
Related examples
- DynamoDB UpdateItem in Java — the same update with AWS SDK for Java 2.x.
- DynamoDB PutItem in Go — replace the whole item instead.
- DynamoDB update expressions —
SET,ADD,REMOVE,DELETE, and idioms. - Understanding ReturnValues — what each
ReturnValuesoption gives you. - "Attribute name is a reserved keyword" — why the alias map here isn't optional.
- "Invalid UpdateExpression" syntax errors — the common SET/ADD syntax mistakes, decoded.
References
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- dynamodb/types package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Update expressions — Amazon DynamoDB Developer Guide
Last verified 2026-07-28 against the official AWS documentation linked above.