CSVFormat CSV Converter

CSV Parsers Compared: PapaParse vs fast-csv vs node-csv, and Which Ones Are Dead

CSVFormat — CSV Formatter & Converter Guides · Updated 2026-10-12 · All guides

"Best CSV parser" is the wrong question. Almost every file people bring to a parser is small enough to hold in memory, and at that size the libraries are interchangeable — they differ on API ergonomics and on how loudly they tell you the file was malformed. The one axis that genuinely separates them is whether you can process the file without loading it.

Download counts are pulled from the npm downloads API on 2026-10-12, 30-day windows at each end of 2024-10-01 → 2026-10-10. Maintenance dates come from the registry the same day. Installs, not users.

The table

PackageFirst 30 daysLast 30 daysChangeLast publish
fast-csv8.983M79.45M+784%2026-10-05
d3-dsv15.35M101.5M+561%2021-06-05
papaparse12.3M72.63M+490%2026-08-24
csv-parse23.43M90.35M+286%2026-09-25
csv-stringify15.81M45.37M+187%2026-09-25
csvtojson3.605M5.726M+59%2025-10-29
csv7.283M11.22M+54%2026-09-25
neat-csv0.53M0.58M+9%2021-10-18

Which ones you should not start with

  • neat-csv — last published 2021-10-18 and grew +9% over two years. Both signals agree: it is done. Fine to leave installed, wrong choice for new code.
  • d3-dsv — last published 2021-06-05 but +561%. That contradiction resolves when you notice it rides inside the D3 ecosystem: the growth belongs to whichever charting stack is fashionable, not to CSV. Its API is also the weakest here — no real error reporting, because it was built for dataviz where a malformed cell is not an incident.
  • csvtojson — +59%, and the job it does is now three lines over any parser above. The flat line is probably not declining CSV usage; it is that middleman dissolving into the parsers.

The real axis: streaming

fast-csv and csv-parse are stream-first. You can point them at a 12 GB export and memory stays flat:

// fast-csv — transform rows as they arrive, constant memory
import { parse } from 'fast-csv';
import { createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

await pipeline(
  createReadStream('huge.csv'),
  parse({ headers: true }),
  async function* (rows) {
    for await (const row of rows) {
      if (row.email) await upsert(row);   // never all in memory at once
    }
  },
);

papaparse is in-memory-first. Its step callback makes it look streaming, but the whole input is a string you already hold, so peak memory is roughly your file size plus whatever data array you accumulate — and in a browser, that string is the parse cost plus the result. It is still the right call in a browser tab, where FileReader gives you a string anyway and there is no stream to pipeline against. That is the honest split:

SituationReach for
Browser, file the user pickedpapaparse
Node, file you cannot hold in memoryfast-csv or csv-parse
Write as well as readcsv-stringify (same project as csv-parse)
One-shot script, size unknownpapaparse, and check the row count after

What every one of them gets wrong

None of these libraries will tell you that 0123456789 was a phone number. dynamicTyping: true (papaparse) and cast: true (csv-parse) turn it into the number 123456789, and the leading zero is gone in whichever output format you chose. The same rule eats a 19-digit transaction ID into floating-point mush and turns 1-5 into a date in every library that is trying to be helpful about it.

The library that casts aggressively is not better than the one that does not; it just loses your data in a way that is harder to notice three steps downstream. Default to strings and cast deliberately, per column, which is what the best-practices page works through — including this site's own tool, whose exact behaviour on ragged rows and blank lines is measured on About rather than guessed.

What the numbers cannot tell you

  • Transitive vs chosen. csv-parse is a dependency of a lot of CLI tools. Its large base is partly other people's software.
  • Correctness. No cell in that table is a statement about RFC 4180 edge cases. Two of these packages handle an embedded newline inside a quoted field differently when the closing quote is missing, and neither number tells you which.
  • Maintenance, until you looked. That is why the last column exists: it is the only column here that is a fact about the package rather than a rumour about its popularity.
Developer Sponsor / Partner
Copied to clipboard!