ValidationException: ExpressionAttributeValues contains invalid value

TL;DR — A value in ExpressionAttributeValues is empty, has an unsupported type, or a :placeholder used in your expression was never defined. Check every :value is present and non-empty.

What it means

Common messages:

ValidationException: ExpressionAttributeValues contains invalid value: One or more parameter values were invalid: An AttributeValue may not contain an empty string for key :s
ValidationException: Value provided in ExpressionAttributeValues unused in expressions: keys: {:x}
ValidationException: An expression attribute value used in expression is not defined; attribute value: :v

Why it happens

  • Empty string / empty binary — historically DynamoDB rejected "". Empty strings are now allowed for non-key attributes (and empty Lists/Maps are fine), but empty values in key attributes and empty Sets are still invalid.
  • Undefined placeholder — your expression references :v but ExpressionAttributeValues has no :v.
  • Unused placeholder — you defined :x but no expression uses it (DynamoDB rejects the whole request).
  • Wrong type — passing a raw JS object/undefined/NaN, or (with the low-level client) the wrong {S}/{N} wrapper.
  • An empty set passed to an ADD/DELETE operation — those clauses take set (or, for ADD, number) operands, and a set can never be empty.

How to fix it

  1. Every :value in the expression must be defined in ExpressionAttributeValues, and every defined value must be used — keep the two in exact sync.
  2. Guard against empty/undefined. Don't pass :v when the source is undefined; drop the clause instead. For sets, ensure at least one member.
  3. Use the Document Client (@aws-sdk/lib-dynamodb) so native JS values are marshalled for you — it removes most type-wrapper mistakes.
  4. Audit the map against the expression string. Print both side by side before the call — every :token in the expression must appear as a key in ExpressionAttributeValues, and every key in the map must appear in the expression.
  5. For low-level clients, validate wire types. An empty set {SS: []} or a missing type wrapper on a key attribute still fails even when the placeholder is defined.

Example

import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, UpdateCommand} from '@aws-sdk/lib-dynamodb';

const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));

const email = getEmail(); // could be undefined
const names = {'#e': 'email'};
const values = {':e': email};

if (email == null) throw new Error('email required'); // don't send :e = undefined

await doc.send(
  new UpdateCommand({
    TableName: 'Users',
    Key: {pk: 'USER#1'},
    UpdateExpression: 'SET #e = :e',
    ExpressionAttributeNames: names,
    ExpressionAttributeValues: values
  })
);

Path in DynoTable

DynoTable's update editor binds values as you type and rejects empty placeholders before the request leaves your machine. Open the item with ⌘K, edit a field, and inspect the generated ExpressionAttributeValues map in the request preview — mismatches show up immediately instead of as a 400 in CloudWatch.

For SDK code you cannot run inline, paste the expression into the Expression Builder and diff its :value map against yours. Switch profiles with ⌘P to test against the same table that threw the error; Test Connection on Settings → Profiles confirms credentials and region. Setup: Connect to AWS, Install. Empty sets and undefined JS values are the most common causes — guard both before the call leaves your process.

Sources

References

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

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.