ExpressionAttributeNames contains invalid key: Syntax error

TL;DR — The problem is the placeholder key on the left side of your ExpressionAttributeNames map, not the attribute it points to. A placeholder must be # followed by plain letters, digits, or underscores (#name, #p0). If you put the real attribute name — with its dots, hyphens, + signs, or spaces — into the placeholder itself, DynamoDB rejects the map. Keep placeholders boring; put the messy real name on the right side.

What it means

ValidationException: 1 validation error detected: ExpressionAttributeNames contains invalid key:
Syntax error; key: "#my.attribute"

# what the engine actually returns, reproduced against DynamoDB Local:
ValidationException: 1 validation error detected: ExpressionAttributeNames contains invalid key: Syntax error; key: "#my.attribute"

ExpressionAttributeNames maps a placeholder token (used inside your expression) to a real attribute name. DynamoDB validates the placeholder's syntax before touching your data: it must start with # and contain only characters that are valid inside an expression token. Special characters that mean something in the expression grammar — . (path separator), -, +, spaces — make the placeholder itself unparseable, and the whole request is rejected with this ValidationException.

Why it happens

  • The real attribute name was copied into the placeholder — e.g. {"#stats.daily": "stats.daily"}. The dot in the key is a syntax error, regardless of what it maps to.
  • Special characters in the placeholder — hyphens (#user-id), + signs, or spaces. Only alphanumerics and underscores are safe after the #.
  • A missing # — keys in ExpressionAttributeNames must begin with #; {"name": "name"} is invalid.
  • A library auto-generating placeholders from attribute names that contain dots or special characters, passing the character straight through.

How to fix it

  1. Use simple placeholders and map each to the real name:

    {
      ExpressionAttributeNames: {'#p0': 'user-id', '#p1': 'stats'},
      KeyConditionExpression: '#p0 = :uid'
    }
  2. For nested paths, alias each segment separately — one placeholder per path element, joined by a literal dot in the expression:

    // read stats.daily where the item has a top-level "stats" map
    {
      ProjectionExpression: '#s.#d',
      ExpressionAttributeNames: {'#s': 'stats', '#d': 'daily'}
    }

    Note the flip side: if the attribute's actual name contains a literal dot (one attribute named "stats.daily", not a nested path), a single placeholder for the whole name is exactly what you want — {'#sd': 'stats.daily'} — so the dot is treated as part of the name, not a path separator.

  3. Check what your wrapper generates — if an ODM/helper builds the map for you, log the final request and inspect the placeholder keys it produced.

  4. Never put path separators in placeholder keys. Dots belong in the expression string between #segment tokens, not inside a single # key.

Check it in DynoTable

DynoTable builds queries with simple #-prefixed placeholders — attribute names with dots, dashes, or reserved words are aliased correctly on the right side of the map. Open a table with ⌘K, add filters, and copy the generated ExpressionAttributeNames.

Cross-check attribute names in the reserved words checker when you hand-write aliases. Switch profiles with ⌘P; see Connect to AWS and Install.

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.