Intermediate3 min read

DynamoDB ReturnValues: Get the Old or New Item From a Write

By default a DynamoDB write returns nothing but success. But you often need the data around the write — the value before you changed it, or the fresh value after. The naive fix is a second GetItem, which is an extra round trip and a race: someone else can write in between. DynamoDB avoids both with the ReturnValues parameter, which hands back the old or new item atomically as part of the write itself.

What does ReturnValues do in DynamoDB?

ReturnValues tells a DynamoDB write to hand back the item as part of the same call, so you skip a second GetItem and the race it creates. PutItem and DeleteItem accept NONE or ALL_OLD; UpdateItem accepts all five (NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW), returning old or new values atomically.

  • ReturnValues returns the item as part of the write — no second read, no race.
  • NONE (default) — return nothing.
  • ALL_OLD — the entire item as it was before the write.
  • UPDATED_OLD — only the attributes the update changed, before values.
  • ALL_NEW — the entire item after the write.
  • UPDATED_NEW — only the changed attributes, after values.
  • PutItem/DeleteItem accept only NONE or ALL_OLD; UpdateItem accepts all five.

The problem: you need the value you just overwrote

Say you run a support desk and an agent changes a ticket's status from open to pending. Your audit log needs to record what the status was before the change. Without ReturnValues you'd:

  1. GetItem to read the current status,
  2. UpdateItem to set the new one.

Between steps 1 and 2 another agent could change the status — now your audit log records a stale "before" value. Worse, it's two calls for one logical operation. ReturnValues collapses it into a single atomic UpdateItem that returns the old status as it actually was at write time.

The five options, and when to use each

UpdateItem supports the full set; the choice is what slice of the item and which side of the write you need:

ReturnValuesReturnsUse when
NONEnothingyou don't need the item back (default)
ALL_OLDwhole item, pre-writeauditing / "what did I just replace?"
UPDATED_OLDchanged attrs, pre-writeyou only care about the fields you touched
ALL_NEWwhole item, post-writeyou need the fresh full item to return to a caller
UPDATED_NEWchanged attrs, post-writereading back a counter/value you just incremented

UPDATED_NEW is the everyday hero: increment a counter with an update expression and read the new total back in the same call, no race. For the support-ticket audit, ALL_OLD (or UPDATED_OLD if you only log the status field) captures the pre-change state atomically.

Note the asymmetry: PutItem and DeleteItem only support NONE and ALL_OLD — there's no "new" value to return for a delete, and a put's new value is just what you sent. Only UpdateItem, which mutates in place, offers all five. AWS documents the exact matrix.

Writing the update in DynoTable

Assemble the UpdateItem and its update expression visually with the DynamoDB expression builder — it emits the SET/ADD clause plus the attribute-name and value maps. In the app, DynoTable shows the resulting item after a staged write is committed, so you see the new state directly.

Reviewing an item's staged change in DynoTable — the old and new values before the update is committed.
Reviewing an item's staged change in DynoTable — the old and new values before the update is committed.

Pitfalls + next steps

  • Don't GetItem-then-write to read around a change — it's a round trip and a race; use ReturnValues.
  • UPDATED_* returns only touched attributes — if you need the whole item, use ALL_*.
  • PutItem/DeleteItem can't return new values — only NONE/ALL_OLD.
  • ReturnValues is not a substitute for a condition — to guard a write, add a condition expression; to read back its effect, use ReturnValues. They compose.
  • Related: update expressions, atomic counters.

Want to make edits and see the before/after without scripting two calls? Download DynoTable and edit your items directly.

Updated