How to Export a DynamoDB Table to CSV (4 Ways)
DynamoDB has no native "export to CSV" button. Every value comes back wrapped in
DynamoDB's marshalled JSON — {"S": "..."},
{"N": "123"}, {"M": {...}} — and a table can hold nested maps, lists, and
sets with no obvious flat-column representation. So "export DynamoDB to CSV" is
really two problems: get the items out, then flatten the typed JSON into
rows. Neither the console nor the managed export does the second step for you.
This guide ranks four approaches by table size and by how much nesting your items carry.
How do I export a DynamoDB table to CSV?
DynamoDB has no native CSV export, so you scan or snapshot the table, then flatten its typed JSON into rows. For a small table, use AWS CLI scan + jq or a short script; for large tables, export to S3 and convert; for a filtered, ready-made CSV, use a GUI like DynoTable.
- Small table, ad-hoc: AWS CLI
scan+jq, or a 20-line script (Method 1 / Method 3). Fine until nested attributes show up. - Large table (GBs+): DynamoDB export to S3 (Method 2), then convert the dump. It runs asynchronously and consumes no read capacity — but it outputs DynamoDB JSON, not CSV.
- Filtered / shaped CSV (a subset of columns, only some items): a GUI export or a script. The managed S3 export gives you the whole table, unfiltered.
Method 1: AWS CLI scan + jq
For a small table you can scan it and reshape the output with jq. A Scan reads
every item in the table and returns it in pages of up to 1 MB; the CLI
follows pagination for you automatically
(AWS docs: Scanning tables).
aws dynamodb scan --table-name MyTable --output json \
| jq -r '.Items[] | [.id.S, .name.S, .price.N] | @csv' \
> out.csvThe catch is in that jq line: you have to hand-write .id.S, .name.S,
.price.N — reaching past each attribute's type descriptor (S, N, B,
BOOL, M, L, SS, NS, BS) to get the raw value. That's manageable for a
flat table with three string columns. It falls apart the moment you have:
- Nested maps/lists —
{"M": {...}}or{"L": [...]}have no single column to flatten into;@csvchokes, or you JSON-encode the cell by hand. - Sets —
{"SS": ["a","b"]}is an array, not a scalar. - Sparse attributes — DynamoDB is schemaless, so item A may have a
priceand item B may not. Your fixed column list silently drops or misaligns columns.
There is also no --output csv that understands DynamoDB types. The CLI's csv
output flattens the raw response, descriptors and all — so you still need jq (or
a script) to strip the type tags. That's the core reason "export DynamoDB table to
CSV via AWS CLI" is never a one-liner past the trivial case.
To export an entire larger table this way without it taking all day, parallelise the
scan with --segment / --total-segments
(AWS docs: Parallel scan —
DynamoDB "assigns items to segments by applying a hash function to each item's
partition key", so segments can be uneven), and read
pagination so you don't stop at the first 1 MB page.
Method 2: DynamoDB export to S3 (large tables)
For tables of any real size, the managed export to Amazon S3 is the right tool. It exports a snapshot from any point in your point-in-time recovery (PITR) window — so PITR must be enabled on the table first — runs asynchronously, and consumes no read capacity units, so it has zero impact on your table's throughput or availability (AWS docs: "Exports are asynchronous, they don't consume read capacity units (RCUs) and have no impact on table performance and availability"; "You need to enable PITR on your table to use the export functionality"). This is also what the console's Exports to S3 action triggers under the hood: the console is just a front end for the same API, so it carries the same PITR requirement and the same JSON output.
aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:us-east-1:123456789012:table/MyTable \
--s3-bucket my-export-bucket \
--export-format DYNAMODB_JSONThe one gotcha: the S3 export does not output CSV. It writes DynamoDB JSON
or Amazon Ion only, as gzipped files in
JSON-lines format (one item per line), plus manifest
files (AWS docs: export output format —
data files are written as .json.gz, "the format is JSON lines", alongside
manifest-summary.json / manifest-files.json). You still need a conversion step
afterwards:
- Athena / Glue read the exported DynamoDB JSON directly — point a table at the
S3 prefix, then write CSV from a
SELECT(this is the usual "export DynamoDB to S3 then to CSV" pipeline). AWS notes that "many AWS services, such as Athena and AWS Glue, will parse this format automatically" (export output format). - Roll your own — decompress the
.gzfiles, parse each JSON line, and flatten it (same flattening problem as every other method).
It's also a full-table snapshot: there's no server-side filter to export only some items. If you need a subset, you either filter after the fact in Athena, or you use a script / GUI instead.
Method 3: a quick script (boto3 / Node)
When you need a shaped CSV — specific columns, a filtered subset, custom handling
for nested fields — a small script beats fighting jq. The win is that the AWS SDKs
unmarshall the typed JSON for you: boto3's resource interface and the JS SDK's
DynamoDBDocumentClient return plain {"price": 2000} instead of
{"price": {"N": "2000"}} (boto3's resource interface makes "the data typing
implicit", per the
AWS Python guide;
the JS DocumentClient "converts annotated response data to native JavaScript types",
per @aws-sdk/lib-dynamodb).
import boto3, csv
table = boto3.resource("dynamodb").Table("MyTable")
rows, resp = [], table.scan()
rows += resp["Items"]
while "LastEvaluatedKey" in resp: # paginate to the end
resp = table.scan(ExclusiveStartKey=resp["LastEvaluatedKey"])
rows += resp["Items"]
with open("out.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["id", "name", "price"])
w.writeheader()
for r in rows:
w.writerow({k: r.get(k) for k in w.fieldnames})You still own two decisions the SDK can't make for you: how to flatten nested
maps/lists into columns (JSON-encode the cell? dot-path the keys?), and what to do
with sparse attributes (here a missing key becomes an empty cell via r.get(k)).
And don't drop the LastEvaluatedKey loop — a single scan() call returns only the
first 1 MB page, so without it you silently export part of the table.
Same caveat as Method 1: a full-table scan here still consumes read capacity and
competes with live traffic. For a big table, prefer
Method 2 and reshape the dump.
Method 4: one-click export in DynoTable
The script and CLI routes work, but you rebuild the same flattening and pagination logic every time. DynoTable does it for you: run or filter a query, then export the visible rows straight to CSV (or Excel) — type descriptors unwrapped, nested maps and lists flattened, sets handled, and only the items and columns you actually want in the output.
Because you export the current view, you get the filtered/shaped CSV that Method 2's full-table snapshot can't give you — without writing a scan loop. It's a desktop DynamoDB client, the same tool you already use to browse the table; see how it compares to other DynamoDB GUIs.
Gotchas: DynamoDB JSON vs flat CSV
Whichever method you pick, the same handful of mismatches between DynamoDB's data model and a flat CSV will bite you:
- Type descriptors. Raw API / CLI / S3-export output wraps every value
(
{"S": "..."},{"N": "123"}). You either unwrap it via an SDK or strip the descriptor yourself. The full set isS,N,B,BOOL,NULL,M,L,SS,NS,BS— see DynamoDB data types. - Nested maps and lists (
M,L) can nest up to 32 levels deep (AWS docs: data types — list and map "can be nested within each other, to represent complex data structures up to 32 levels deep") and have no natural single-column form. Decide up front: JSON-encode the cell, or explode nested keys into dot-pathed columns (address.city). - Sets (
SS/NS/BS) are unordered collections, not scalars — AWS warns "the order of the values within a set is not preserved" (data types) — so flatten to a delimited string and don't rely on element order. - Sparse attributes. DynamoDB is schemaless, so two items can have different attributes. There is no fixed column set; union the keys across all items or columns will misalign. This is a direct consequence of single-table design, where one table holds several entity shapes.
- Pagination.
Scan(andQuery) return at most 1 MB per call. If you don't loop onLastEvaluatedKeyyou'll silently export only the first page. See pagination. - Number precision. DynamoDB numbers carry up to 38 digits of precision and travel as strings (AWS docs: data types: "Numbers can have up to 38 digits of precision"; "All numbers are sent across the network to DynamoDB as strings"); spreadsheet software may coerce long numbers or IDs into floats and lose digits. Keep them as text.
FAQ
How do I export a DynamoDB table to CSV with the AWS CLI?
Scan the table and reshape the output with jq (Method 1): aws dynamodb scan →
jq to strip each value's type descriptor → @csv. There's no DynamoDB-aware
--output csv, so you always do the type-stripping yourself, and it breaks on nested
maps, lists, and sets.
Can I export a DynamoDB table directly to CSV from AWS?
Not in one step. The console and the managed S3 export both produce DynamoDB JSON or
Amazon Ion, never CSV. You always need a conversion step — CLI + jq, a script,
Athena/Glue over the S3 dump, or a GUI that does the flattening for you.
How do I export an entire DynamoDB table without affecting production?
Use the export to S3 feature (Method 2). It runs asynchronously and consumes
no read capacity units, so it doesn't compete with live traffic — unlike a
Scan, which is metered against your table's throughput
(AWS docs).
It requires PITR to be enabled and exports the full table, not a filtered subset.
How do I export DynamoDB to S3 as CSV?
The managed export only writes DynamoDB JSON / Ion to S3, so "to CSV" is a second
hop: register the export prefix as an Athena (or Glue) table and write CSV from a
SELECT. There's no --export-format CSV.
How do I export DynamoDB to Excel?
Export to CSV first (any method above), then open the CSV in Excel — keeping long
numeric IDs as text so they aren't coerced to floats. There's no direct .xlsx
export from DynamoDB; a GUI like DynoTable can save the current view
straight to a spreadsheet-ready CSV.
Why does my exported JSON have {"S": ...} and {"N": ...} everywhere?
That's DynamoDB's marshalled wire format — each value is tagged with a type
descriptor. Unmarshall it with an SDK, the
DynamoDB JSON converter, or a GUI before writing
CSV. The wire format is the same whether the data came from the API, the CLI, or the
S3 export.
Browse, filter, and export your own tables to CSV with DynoTable, or unwrap a sample of DynamoDB JSON in the JSON converter first.