DEV Community

Cover image for How to Export a DynamoDB Table to CSV (4 Ways)
DynoTable
DynoTable

Posted on Edited on Originally published at dynotable.com

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 starts with the way that does both steps for you, then covers the
three AWS-tooling routes and when each one is the right call.

How do I export a DynamoDB table to CSV?

Open the table in DynoTable, filter to the rows
you want, and export the result to CSV in one click — type descriptors
unwrapped and nested values flattened for you
(Method 1). Building it from AWS
tooling instead: scan with the CLI and reshape with jq for a small table
(Method 2), use the managed S3 export for large
tables (Method 3), or write a
short script when you need custom shaping
(Method 4).

  • Filtered / shaped CSV (a subset of columns, only some items): a GUI export (Method 1) or a script. The managed S3 export gives you the whole table, unfiltered.
  • Small table, ad-hoc, terminal-only: AWS CLI scan + jq (Method 2). Fine until nested attributes show up.
  • Large table (GBs+): DynamoDB export to S3 (Method 3), then convert the dump. It runs asynchronously and consumes no read capacity — but it outputs DynamoDB JSON, not CSV.

Note

No AWS-native path lands you on CSV directly. The console, the CLI, and the
managed export all emit DynamoDB-typed JSON; CSV is always a conversion step
you own
— unless a tool does the flattening for you.

Method 1: one-click export in DynoTable

DynoTable treats export as part of browsing: run or
filter a query, press ⌘⇧E
(or the Export button in the tab toolbar), and pick what goes out:

  • Formats: CSV (one row per item, with headers — sets serialize as JSON arrays inside a cell), JSON, and NDJSON. JSON/NDJSON come unmarshalled (plain "count": 3) or marshalled DynamoDB-JSON when you need a lossless round-trip that preserves large numbers.
  • Scopes: the currently loaded rows, just your selection, or the full filter match — every item your query matches, streamed straight from DynamoDB rather than only what's on screen. That last one is the filtered export the managed S3 snapshot can't do.
  • Destination: clipboard or file. For one-off grabs, right-click a row and Copy as… puts CSV, JSON, NDJSON, or DynamoDB-JSON straight on the clipboard, no dialog.

DynoTable's export dialog: format (CSV / JSON / NDJSON), scope from selection to full filter match, and clipboard or file destination.

The flattening problems that break the DIY routes below are handled: type
descriptors are unwrapped, nested maps and lists are flattened, and renamed
columns flow through to the CSV headers. Large exports detach and run in
the background
— streamed to disk row by row, surviving tab switches and
even an app reload — so a multi-gigabyte export never has to fit in memory.

It's a desktop DynamoDB client, the same tool you already use to browse the
table; see how it compares to other DynamoDB GUIs.
When would you not use it? When the export must run unattended in a
pipeline — that's what the script route
(Method 4) is for.

Method 2: 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.csv
Enter fullscreen mode Exit fullscreen mode

The 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; @csv chokes, 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 price and item B may not. Your fixed column list silently drops or misaligns columns.

There is also no --output csv at all — the CLI's output formats are json,
yaml, text, table, and off, none of which understand DynamoDB types. 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.

Warning

Scan is metered on everything it reads, before any filter is applied — a
--filter-expression does not lower the cost (AWS docs:
"a Scan consumes the same amount of read capacity, regardless of whether a filter
expression is present"). On a large table this both runs
up your read bill and competes with production traffic. See
Query vs Scan for why. For anything beyond a small table,
use Method 3.

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 3: 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, or the export fails with
PointInTimeRecoveryUnavailableException
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_JSON
Enter fullscreen mode Exit fullscreen mode

The 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
afterward:

  • 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 .gz files, 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 GUI (Method 1) / script instead.

Note

Full exports are billed on the table's data size at the export point in time.
Incremental exports are billed on the changed data processed, with a 10 MB
minimum charge
, plus the usual S3 storage and PUT request costs
(AWS docs:
"Incremental export has a minimum charge of 10MB. Additional charges apply for storing
exported data in Amazon S3 and for PUT requests"). Check current rates on the
DynamoDB pricing page before exporting a
large table.

Method 4: a quick script (boto3 / Node)

When the export must run unattended — a nightly job, a CI step — a small
script beats everything above. 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})
Enter fullscreen mode Exit fullscreen mode

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 2: a full-table scan here still consumes read capacity and
competes with live traffic. For a big table, prefer
Method 3 and reshape the dump.

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 is S, 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 (and Query) return at most 1 MB per call. If you don't loop on LastEvaluatedKey you'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

What's the fastest way to export a DynamoDB table to CSV?
A GUI that does the flattening for you: in DynoTable, filter the
table, press ⌘⇧E, pick CSV, and choose a scope — from the selected
rows up to every item the filter matches, streamed from DynamoDB. Type
descriptors and nested values are handled automatically.

How do I export a DynamoDB table to CSV with the AWS CLI?
Scan the table and reshape the output with jq (Method 2): 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 3). 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; DynoTable saves 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.

Top comments (0)