DynamoDB TTL attribute must be a Number

TL;DR — DynamoDB's Time to Live only deletes an item when its designated TTL attribute holds a Number representing a Unix epoch timestamp in seconds. A String like "1735689600", a millisecond value, an ISO-8601 date, or a missing attribute is silently ignored — the item never expires. Store the TTL as an epoch-seconds Number and re-write the affected items.

What it means

# UpdateTimeToLive within one hour of a prior change:
ValidationException: Time to live has been modified multiple times within a fixed interval

# The quieter failure — no error at all, item just never expires:
TTL attribute "expiresAt" = "2026-01-01T00:00:00Z"   ← String, ignored
TTL attribute "expiresAt" = 1735689600000            ← milliseconds, treated as year 56895

Enabling TTL (UpdateTimeToLive) succeeds even when the attribute doesn't yet exist or is the wrong type — DynamoDB does not type-check it up front. The failure shows up later: the background TTL process only deletes an item when the attribute is a Number whose value is a past epoch-seconds timestamp. Anything else is treated as "no expiry".

Why it happens

  • Stored as a String — the value is {"S": "1735689600"} instead of {"N": "1735689600"}. TTL ignores non-N types.
  • Milliseconds instead of secondsDate.now() (JavaScript) returns milliseconds; a 13-digit value is thousands of years in the future, so the item effectively never expires.
  • An ISO-8601 / human date string rather than epoch seconds.
  • A different attribute name than the one registered with TTL (the name is case-sensitive).
  • Calling UpdateTimeToLive again too soon — a second change within roughly one hour of the first raises a ValidationException.

How to fix it

  1. Write the TTL value as a Number in epoch secondsMath.floor(Date.now() / 1000) + ttlSeconds in JavaScript, int(time.time()) + ttl in Python. Never store milliseconds.
  2. Use the N type, not S. With the low-level client that's {"N": "1735689600"}; the Document Client marshals a native number for you.
  3. Match the registered attribute name exactly, including case. Confirm it with DescribeTimeToLive.
  4. Backfill existing items — items written before the fix still carry the bad value; re-write them with a correct epoch-seconds Number.
  5. Wait ~1 hour between TTL config changes to avoid the multiple-modifications ValidationException.

Want to see the wire type of every attribute while you browse a table? The DynoTable desktop app renders N/S/M type tags inline, so a TTL stored as a String jumps out before it costs you an un-expired item.

FAQ

Why isn't my DynamoDB TTL deleting items? The TTL attribute must be a Number holding a Unix epoch timestamp in seconds. A String value, a milliseconds value, an ISO date, or a name that doesn't match the registered TTL attribute are all silently ignored, so the item never expires. Expiry can also lag up to ~48 hours after the timestamp passes.

Does DynamoDB validate the TTL attribute type when I enable TTL? No. UpdateTimeToLive succeeds even if the attribute is missing or the wrong type. The type requirement (Number, epoch seconds) is only enforced by the background deletion process, which is why a bad TTL fails quietly.

Console olmadan DynamoDB ile çalışın

DynoTable, DynamoDB için hızlı bir masaüstü istemcisidir — tablolara göz atın, SQL tarzı sorgular çalıştırın ve item'ları yerel olarak düzenleyin.