DynamoDB PutItem in Node.js (AWS SDK v3)
PutItem writes a whole item and replaces any existing item with the same primary key (item-based actions covers how that differs from UpdateItem). The v3 client sends DynamoDB JSON directly, so Item holds { S: … } / { N: … } values rather than plain JavaScript.
Code
import {DynamoDBClient, PutItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const command = new PutItemCommand({
TableName: 'Music',
Item: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'},
AlbumTitle: {S: 'Danzon'},
Year: {N: '1994'},
Awards: {N: '0'}
},
ConditionExpression: 'attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)',
ExpressionAttributeNames: {
'#cond0': 'Artist',
'#cond1': 'SongTitle'
}
});
try {
await client.send(command);
console.log('Song written');
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
console.log('A song with that key already exists — not overwritten');
} else {
throw err;
}
}Explanation
err.name is the right check, and it is not the only thing on the error. Catching the failed condition above and printing the object gave:
err.name ConditionalCheckFailedException
err instanceof Error true
err.message The conditional request failed
err.$metadata.httpStatusCode 400Every v3 error carries $metadata with the status code, the request id and the attempt count, which is what you want in a log line. err.name is stable across the modular packages; instanceof ConditionalCheckFailedException also works but pulls the class in as a value import, so bundlers keep it.
The error can hand you the item that blocked the write. Add ReturnValuesOnConditionCheckFailure: 'ALL_OLD' to the command and err.Item arrives populated: five attributes in the run above, with Year as {"N":"1994"}. Most create-only handlers do a GetItem after the failure to find out what was already there. That round trip is avoidable. (ReturnValues: 'ALL_OLD' is the success-path cousin; ReturnValues covers the rest.)
marshall() refuses more input than you expect. Swapping this page's typed Item for DynamoDBDocumentClient and plain objects is the usual next step, and @aws-sdk/util-dynamodb is strict by default. Three real throws, verbatim:
{Genre: undefined} Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.
{tags: new Set()} Pass a non-empty set, or options.convertEmptyValues=true.
{n: 9007199254740993} Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.The first is the one that reaches production: an optional field that is undefined rather than absent throws at marshal time, and removeUndefinedValues: true in DynamoDBDocumentClient.from(client, {marshallOptions}) is the standard fix.
Read the third line again. The literal was 9007199254740993; the message quotes 9007199254740992. JavaScript had already rounded the value before the SDK ever saw it, so the SDK is reporting what it received. This is the whole reason DynamoDB transports N as a string: it holds 38 digits of precision, and a JS number holds 15 to 17. Anything that is really an identifier belongs in S, and anything that is really a decimal belongs in NumberValue or a string you format yourself.
ConditionExpression costs write capacity even when it says no. AWS: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (fetched 2026-07-28). A tight create-only retry loop is billed per attempt. For calibration, a successful put of a ~15 KB item reported "CapacityUnits": 15; writes round up per 1 KB rather than the 4 KB reads use.
The aliases are load-bearing. #cond0/#cond1 resolve to Artist/SongTitle through ExpressionAttributeNames. Inline names work until one collides with a reserved word, and then the expression fails on an attribute you did not touch.
Do it visually
The marshalling rules above are easiest to check by seeing both forms side by side. The free DynamoDB JSON converter turns plain JSON into the typed { S: … } form and back, so you can confirm what marshall() would have produced before you send it.
To write and edit items against your own tables — a form per attribute, type pickers, copy the result back out as SDK v3 code — download DynoTable.
Related guides
- DynamoDB condition expressions —
attribute_not_exists, optimistic locking, and more. - DynamoDB data types — how each attribute type is written.
- DynamoDB ConditionalCheckFailedException — what the create-only condition throws when the item already exists.
- DynamoDB ValidationException — the catch-all for a malformed item or expression.
References
- PutItem — Amazon DynamoDB API Reference
- PutItemCommand — AWS SDK for JavaScript v3 Reference
- Capacity unit consumption — Amazon DynamoDB Developer Guide
- Condition expressions — Amazon DynamoDB Developer Guide
- Working with items and attributes — Amazon DynamoDB Developer Guide
Reproduced 2026-07-28 on Node v24.18.0 with @aws-sdk/client-dynamodb 3.1095.0 and @aws-sdk/util-dynamodb 3.996.7, against DynamoDB Local (amazon/dynamodb-local) on port 9000. The error strings, the object shape and the capacity reading are captured output, copied verbatim.