DynamoDB TransactWriteItems in Python (boto3)

transact_write_items groups up to 100 writes into one all-or-nothing operation — either every action commits, or none do. With the boto3 low-level client you pass a TransactItems list mixing Put, Update, Delete, and ConditionCheck actions, each with an optional condition.

Code

import boto3

client = boto3.client("dynamodb")

# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
    client.transact_write_items(
        TransactItems=[
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
                    "UpdateExpression": "SET #upd0 = #upd0 - :one",
                    "ConditionExpression": "#upd0 >= :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}},
                }
            },
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
                    "UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
                }
            },
        ]
    )
    print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
    # One reason per action, in TransactItems order. Code "None" means that
    # action was fine — some OTHER action sank the transaction.
    codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
    print(f"Transaction canceled: {codes}")  # e.g. ['ConditionalCheckFailed', 'None']

Explanation

  • TransactItems — up to 100 Put / Update / Delete / ConditionCheck actions, 4 MB aggregate, values in DynamoDB JSON. Actions can span tables (same account + Region), but no two actions may target the same item — that alone cancels the transaction.
  • ConditionCheck — asserts a condition on an item the transaction does not modify (e.g. "the account still exists") and vetoes the whole transaction if it fails.
  • TransactionCanceledException — the one error to handle. e.response["CancellationReasons"] lists one {"Code", "Message"} per action in order — codes include ConditionalCheckFailed, TransactionConflict (another transaction touched an item mid-flight — safe to retry with backoff), ProvisionedThroughputExceeded, and ValidationError; None marks the innocent actions. Set "ReturnValuesOnConditionCheckFailure": "ALL_OLD" on an action to get the losing item's attributes in its reason.
  • ClientRequestToken — pass one to make the call idempotent for 10 minutes; a botocore retry after a network timeout then can't apply the writes twice.
  • Cost — transactional writes consume two underlying writes per item (prepare + commit), so they bill roughly 2× the WCUs of a plain write. Don't reach for transactions when a single-item conditional write already gives you atomicity on one item.

Do it visually

The condition and update expressions inside each action are the fiddly part — the DynamoDB Expression Builder assembles them with the name/value maps and copies runnable boto3 code.

To edit items and review the generated expressions in a GUI — download DynoTable.

References

Last verified 2026-07-13 against the official AWS documentation linked above.

Work with DynamoDB without the Console

DynoTable is a fast desktop client for DynamoDB — browse tables, run SQL-style queries, and edit items locally.