Beginner6 min read

DynamoDB Data Types: Every Type, with Examples

Every DynamoDB attribute is tagged with a one- or two-letter type code in the wire format. Knowing the set matters because the type drives both how a value is stored and how it counts toward an item's size.

What data types does DynamoDB support?

DynamoDB supports ten data types across three categories. Scalars are String (S), Number (N), Binary (B), Boolean (BOOL), and Null (NULL). Documents are Map (M) and List (L), which nest other types. Sets are String Set (SS), Number Set (NS), and Binary Set (BS) — unordered, homogeneous, and non-empty. Only S, N, and B can be a key.

CodeTypeCategoryJSON / JS equivalentExample (DynamoDB-JSON)
SStringScalarstring{"S": "Ada"}
NNumberScalarnumber{"N": "37"}
BBinaryScalarUint8Array / base64{"B": "ZGF0YQ=="}
BOOLBooleanScalarboolean{"BOOL": true}
NULLNullScalarnull{"NULL": true}
MMapDocumentobject{"M": {"k": {"S": "v"}}}
LListDocumentarray{"L": [{"N": "1"}]}
SSString setSet— (no JSON type){"SS": ["a", "b"]}
NSNumber setSet{"NS": ["1", "2"]}
BSBinary setSet{"BS": ["ZA=="]}

Scalars

  • S — string (UTF-8; sized by its byte length, not character count).
  • N — number, sent as a string for precision; up to 38 digits.
  • B — binary, sent base64-encoded.
  • BOOLtrue / false.
  • NULL — an explicit null marker.

Documents

  • M — map (object). Nested attributes each keep their own type tag.
  • L — list. Elements may be mixed types.
{"profile": {"M": {"name": {"S": "Ada"}, "age": {"N": "37"}}}}

Sets

  • SS — string set, NS — number set, BS — binary set.

Sets are unordered, homogeneous, and can't be empty. Crucially, plain JSON has no set type — an array round-trips as a list (L), never an SS/NS. That's a real conversion limitation, not a bug; see the DynamoDB-JSON converter note.

Which types can be a key?

and keys — on the table and on any index — must be a scalar, and only S, N, or B. You can't key on a boolean, set, map, or list. Model a "composite" key by concatenating values into one S (e.g. ORDER#2026#42).

Limits worth knowing

  • An item maxes out at 400 KB — every attribute name plus value, including nested ones.
  • Numbers carry up to 38 digits of precision (positive or negative).
  • Maps and lists nest up to 32 levels deep.
  • Sets are non-empty and homogeneous — no empty set, no mixing S and N.

Why the type affects cost

Item size is the sum of attribute-name bytes plus value bytes, and each type sizes differently — numbers are compacted, booleans and nulls are 1 byte, maps and lists add per-element overhead. That size rounds up to read/write capacity units. Measure a real item with the item-size calculator.

Do it in DynoTable

The set-vs-list distinction above is the thing tooling usually hides. DynoTable's item editor makes it explicit with a format toggle:

  • Plain JSON — primitives stay plain ("age": 30), but sets keep their type wrapper so they survive the round-trip: "tags": { "SS": ["a", "b"] }, "scores": { "NS": ["1.5", "2.5"] }. This is the readable form for everyday editing.
  • DynamoDB JSON — the canonical AWS form, where every value carries its type tag: "age": { "N": "30" }, "name": { "S": "alice" }.

Switching between them shows you exactly how each scalar, document, and set type is represented on the wire — and because the set types have no plain-JSON equivalent, the toggle is the only way to author an SS/NS/BS by hand without hand-marshalling the whole item.

DynoTable's item editor showing an item with a number set and a string set, and the Plain-JSON / DynamoDB-JSON toggle.
DynoTable's item editor showing an item with a number set and a string set, and the Plain-JSON / DynamoDB-JSON toggle.

Try DynoTable to see every attribute's type and the live byte count as you edit an item — and to filter or aggregate across typed attributes in the SQL Workbench, which reads each type tag for you. To convert a marshalled blob without the app, the DynamoDB-JSON converter does the same round-trip in the browser.

Picking a type on purpose

The wire format is not neutral — it constrains keys, size, and how you query.

You need…PreferAvoidWhy
A primary or index keyS, N, or BBOOL, M, L, setsKeys must be scalar S/N/B
Exact decimal moneyN as stringJSON number in app code38-digit precision on the wire
Unique tags on an itemSSL of stringsSets dedupe; lists allow duplicates
Ordered historyL or a sort keySSSets are unordered
Nested profile blobMFlattened S JSONMaps keep typed children
Binary thumbnailBBase64 in SB is the native binary type

A common mistake is storing tags: ["a","b"] in plain JSON, letting the SDK marshal it as L, then wondering why a ConditionExpression expecting SS never matches. The item-size calculator and DynoTable's format toggle make that visible before the write lands.

Size example with real units

Take a minimal user profile:

{
  "pk": {"S": "USER#42"},
  "email": {"S": "a@example.com"},
  "plan": {"S": "pro"},
  "score": {"N": "1280"}
}

Paste the item into the item-size calculator. A typical result is roughly 80–120 bytes depending on attribute names — well under one 4 KB read block, so a GetItem costs 1 eventually-consistent RCU (half that if you accept eventual consistency on the base table). Add a 2 KB bio map and the same read still stays in one block until you cross 4 KB.

Nested maps charge per element: each child attribute name plus typed value adds bytes. A deeply nested M inside M tree can push an item toward the 400 KB cap faster than flat attributes with the same information.

Numbers ride as strings for a reason

DynamoDB stores N values as decimal strings with up to 38 digits. JavaScript numbers are IEEE-754 doubles — roughly 15–17 significant digits. Marshalling 9007199254740993 through plain JSON can silently round; marshalling through DynamoDB JSON preserves the exact string "9007199254740993".

When you author expressions that compare numbers, placeholders still use string forms in ExpressionAttributeValues: {":n":{"N":"42"}}. The expression builder emits those maps correctly so you do not mix untagged literals into a ConditionExpression.

Sets in application code

Because JSON lacks a set type, application code often uses arrays. If the domain requires uniqueness — tag sets, role sets, id sets — write SS/NS/BS explicitly. DynoTable's Plain JSON mode keeps set wrappers visible during edit; the DynamoDB JSON toggle shows the canonical { "SS": [...] } form.

For query access, remember sets are not keys. Model lookup ids as S or N keys and store set-valued attributes as payload fields filtered after the key condition — or denormalize membership into adjacency items if you need to query by membership at scale.

Practice path in DynoTable

Connect a table, open any item, and flip Plain JSON ↔ DynamoDB JSON while watching the live byte counter. Change an L to an SS and commit — the diff makes the type tag explicit. Run a SQL Workbench SELECT over typed columns; strings, numbers, and maps surface as distinct types in the result grid, which helps when imported CSV data assumed everything was a string.

Download DynoTable to edit with typed controls instead of guessing attribute descriptors in the CLI.

Updated