Intermediate7 min read

The Type Attribute in DynamoDB

In SQL, a row's table is its type — a row in documents is a document. A DynamoDB single table mixes every entity under one schema, so an item carries no built-in answer to "what is this?".

The Type attribute puts that answer back: a plain string on every item naming the entity it represents.

What is the Type attribute in DynamoDB?

The Type attribute is a plain string you stamp on every item — like EntityType: "Document" — naming the entity that item represents. Because a single table mixes many entities under one schema, items carry no built-in type. The Type puts it back, so your code identifies rows, filters a GSI to one entity, and survives migrations.

  • Stamp a Type on every write. One attribute — EntityType: "Document" — on every item, no exceptions. It costs a few bytes and saves you later.
  • It identifies entities in a mixed partition. A Query returns workspaces, documents, and comments together; the Type tells your code which is which without parsing key prefixes.
  • It powers single-entity filtering on a GSI. Project the Type into an index and you can narrow an overloaded index to exactly one entity type.
  • It's your escape hatch for migrations. When you export to re-model or move an entity to its own table, the Type is the column you split on.

Why a mixed table loses the type

Single-table design stores every entity in one table behind generic keys like PK and SK. That's the whole point — one Query returns a parent and its children together. But it means a partition is heterogeneous.

Take a SaaS doc-collaboration app. One workspace partition holds the workspace record, its documents, and the comments on those documents:

PKSKattributes
WS#acmeMETAname, plan, seats
WS#acmeDOC#a1#METAtitle, owner, wordCount
WS#acmeDOC#a1#CMT#0007author, body, createdAt
WS#acmeDOC#a1#CMT#0008author, body, createdAt

Query PK = "WS#acme" hands back all four items in one billed read. Now your code has a list of raw items and no reliable way to say which is a document and which is a comment — short of string-matching the SK, which is brittle the moment your key format changes.

Stamp the Type on every item

The fix is one attribute on every write, naming the entity:

PKSKEntityTypetitle
WS#acmeMETAWorkspace
WS#acmeDOC#a1#METADocumentQ3 Roadmap
WS#acmeDOC#a1#CMT#0007Comment

Branching on item.EntityType === "Document" is a stable equality check. Parsing SK.startsWith("DOC#") && SK.includes("#CMT#") is a guess that breaks when you rev the key. The Type decouples your read logic from your key encoding — that's the real win.

Query PK = 'WS#acme'Mixed partitionEntityType: 'Workspace'EntityType: 'Document'EntityType: 'Comment'Route by Type

One read returns three entity types; the Type attribute routes each item to the right handler without touching the keys.

Filter a GSI down to one entity

The Type earns its keep on indexes. Say you add a GSI keyed on GSI1PK = WS#acme, GSI1SK = updatedAt to list "everything recently changed in this workspace, newest first". An overloaded index sweeps in documents and comments — but a feed UI may want documents only.

Two ways to narrow it, and the difference is money:

ApproachWhat it costsWhen to use
FilterExpression on TypeReads all matching items, bills for all of them, drops non-matches after readMixed entities are rare in the result; quick to ship
Sparse index (Type in GSI1PK)Only the entity you want ever lands in the indexOne entity dominates; you want zero waste

A FilterExpression runs after items are read and after capacity is consumed — AWS is explicit that filtering doesn't reduce read cost (DynamoDB Developer Guide: FilterExpression). Filtering on Type is honest, not free: you pay for the comments you throw away.

To narrow the feed to documents, the query carries a condition on the Type attribute. Assemble the FilterExpression, names, and values with the DynamoDB expression builder — it emits the #t = :doc placeholder so you don't fat-finger a reserved word.

KeyConditionExpression     GSI1PK = :ws
FilterExpression           #t = :doc
ExpressionAttributeNames   { "#t": "EntityType" }
ExpressionAttributeValues  { ":ws": "WS#acme", ":doc": "Document" }

Want the index to carry only documents and skip the filter entirely? Write GSI1PK only on document items — a sparse index. Items without the GSI key never replicate into the index, so the read touches documents alone. The Type attribute is what tells your writer which items qualify.

Keep the value stable and singular

Pick the value once and treat it as an enum. Document, never sometimes Doc and sometimes document — a drifting value is worse than no value, because your equality checks pass on one casing and silently miss the other.

One Type per item. If an item feels like two entities, that's usually a modelling smell — it should be two items, each in its own collection or sort-key range, not one row wearing two hats.

The migration payoff

The reason to stamp the Type before you need it: re-modelling. The recommended re-model path is export, transform, reimport — and AWS documents bulk export to S3 for exactly this kind of offline reshaping (Exporting DynamoDB to S3).

When that day comes, the Type is the column you GROUP BY. Want to lift comments into their own table, or renormalise the export into per-entity files for an analytics warehouse? You split the dump on EntityType. Without it, you're back to reverse-engineering keys across millions of rows.

Next steps

The Type attribute is cheap insurance: identify entities in a mixed read, filter an overloaded GSI, and split cleanly when you re-model. Stamp it on every write from day one — retrofitting it onto a live table means a full backfill.

Related reading: single-table design for the mixed-partition pattern this serves, GSI vs LSI for choosing the index shape behind a sparse index, and Query vs Scan for why a FilterExpression never saves you read cost.

Build the filter on the Type with the DynamoDB expression builder, and try DynoTable to browse a real mixed-entity table and see the Type column line up across every item.

Updated