JavaScript CSV to array: read & parse CSV into arrays (browser & Node.js)

javascript csv to array and read csv file into array: 1D lists, 2D row arrays, and header-keyed objects; javascript parse csv with split for simple data, BOM and quotes, fetch and FileReader, Node readline and csv-parser, Papa Parse and d3.csvParse, plus csv to javascript pitfalls. Runnable snippets tested on Node.js v20.18.2.

Published

Updated

Read time 10 min read

Reviewed byDeepak Prasad

JavaScript CSV to array: read & parse CSV into arrays (browser & Node.js)

javascript csv to array, read csv file into array, and csv to array javascript all describe the same job: turn delimiter-separated text into something you can index in code—usually strings first, then optionally typed fields. javascript parse csv in the browser starts from a string (fetch or FileReader); in Node it starts from fs or a stream.

Tested on: Node.js v20.18.2. A short note after each runnable snippet describes what you should see in the console.


Quick reference

Situation Practical approach
Single column of values split('\n'), drop header row
Grid of strings lines.map(line => line.split(',')) if data is simple
Rows as objects with headers First line as keys, map remaining lines
Real files (quotes, BOM, Excel) Papa Parse or d3.csvParse
Large files on server readline, csv-parser, or Papa stream

Pick a shape for your data

Before you javascript parse csv, pick the shape that matches how the rest of your app will consume rows:

  • 1D array — one value per line after a header (lists of ids, names, etc.).
  • 2D array — each inner array is one row; every cell stays a string unless you convert later.
  • Array of objects — first row becomes keys; each later row becomes { header: value }, which reads cleanly in UI code.

The reverse direction, javascript array to csv export, is answered in the page FAQ.


One-dimensional array (single column)

Use this when the file is a single column (plus an optional header): each line becomes one string in the array after you drop the header row.

javascript
const csv = `Name
Alice
Bob
Charlie`;

const rows = csv.split('\n');
const oneDimensionalArray = rows.slice(1); // remove the header

console.log(oneDimensionalArray);
Output

You should see one line: an array of three strings—Alice, Bob, and Charlie—in that order.


Two-dimensional array (rows × columns)

Use this when every row has the same delimiter and no field contains that delimiter (no quoted commas). You get an array of rows, each row an array of cell strings.

javascript
const csv = `Name,Age,Job
Alice,30,Engineer
Bob,40,Doctor`;

const rows = csv.split('\n');
const twoDimensionalArray = rows.map((row) => row.split(','));

console.log(twoDimensionalArray);
Output

You should see one line: a nested array whose first row is the header cells, then one row per data line (all values still strings).


Array of objects (header keys)

This pattern maps the first line to property names and each following line to one object. It is easier to consume in components than numeric column indexes (see JavaScript arrays and unique values for follow-on operations).

javascript
const csv = `Name,Age,Job
Alice,30,Engineer
Bob,40,Doctor`;

const rows = csv.split('\n');
const headers = rows[0].split(',');
const arrayOfObjects = rows.slice(1).map((row) => {
  const values = row.split(',');
  const obj = {};
  headers.forEach((header, index) => {
    obj[header] = values[index];
  });
  return obj;
});

console.log(arrayOfObjects);
Output

You should see one line: two objects whose keys match the header row (Name, Age, Job).


When split(',') is not enough

Naive csv to js array logic breaks when:

  • A field contains the delimiter, for example "Engineer, Senior".
  • Excel or Windows uses \r\n line endings (a stray \r can stick to the last cell).
  • The file starts with a UTF-8 BOM (\uFEFF) before the first header name.

For production javascript parse csv workloads, prefer a real parser (below). For a small inline fix, strip the BOM then split lines with a regex that accepts both \n and \r\n:

javascript
const raw = '\uFEFFName,Age\nAlice,30';
const text = raw.replace(/^\uFEFF/, '');
const rows = text.split(/\r?\n/).map((line) => line.split(','));

console.log(rows);
Output

You should see a two-row grid: headers Name / Age, then Alice / 30, with no BOM character stuck on Name.

Minimal quoted-field parser (single-line rows)

If fields may contain commas but do not contain embedded newlines inside quotes, a small state machine is enough:

javascript
const csv = `Name,Age,Job
Alice,30,"Engineer, Senior"
Bob,40,"Doctor"
"Charlie, Jr.",50,Teacher`;

const rows = csv.split('\n');

rows.forEach((row) => {
  let insideQuotes = false;
  let field = '';
  const fields = [];

  for (const ch of row) {
    if (ch === '"') {
      insideQuotes = !insideQuotes;
    } else if (ch === ',' && !insideQuotes) {
      fields.push(field);
      field = '';
    } else {
      field += ch;
    }
  }
  fields.push(field);

  console.log(fields);
});
Output

You should see four log lines—one per CSV row—where quoted commas stay inside a single field (for example Engineer, Senior).


Manual parsing from a string (same as fetch / File text)

Whether the text came from fetch, FileReader, or Node fs.readFile, the split approach is the same once you have a string (see also string → array patterns).

javascript
const csvString = 'Name,Age,Job\nAlice,30,Engineer\nBob,40,Doctor';
const lines = csvString.split('\n');
const array = lines.map((line) => line.split(','));

console.log(JSON.stringify(array));
Output

You should see one JSON string: a header row plus two data rows, every cell still a string.

fetch (browser or Node 18+)

fetch returns a Response; call text() to get the CSV body as a string, then reuse the same row and field splitting as above.

javascript
fetch('/data.csv')
  .then((response) => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.text();
  })
  .then((data) => {
    const lines = data.split(/\r?\n/).filter(Boolean);
    return lines.map((line) => line.split(','));
  })
  .then((array) => console.log(array))
  .catch((err) => console.error(err));

Use the same parsing as above once response.text() resolves. Cross-origin fetch URLs require CORS to allow the response.


Papa Parse follows RFC 4180 more closely than naive split, supports header rows, dynamic typing, large files, and Node streams. Use it when user uploads or API responses can contain quoted commas, inconsistent line endings, or malformed rows you want reported in results.errors.

Install:

bash
npm install papaparse

Or load in HTML (version pinned here to match npm’s current major line):

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.5.3/papaparse.min.js"></script>

Parse a CSV string

Papa.parse on a string runs synchronously and returns data plus errors and meta (delimiter, linebreak, fields, and so on).

javascript
const Papa = require('papaparse');

const csvString = 'Name,Age,Job\nAlice,30,Engineer';
const result = Papa.parse(csvString, {
  header: true,
  dynamicTyping: true,
});

console.log(JSON.stringify(result.data));
console.log(JSON.stringify(result.errors));
Output

You should see two lines: first, a JSON array with one object (Age is a number because dynamicTyping is on); second, an empty errors array.

Malformed quoted CSV (errors live on results.errors)

Papa Parse does not use an error: callback on the config for parse-time issues; inspect result.errors (and often result.data is empty when parsing aborts early):

javascript
const Papa = require('papaparse');

const incorrectCSV = 'Name,Age,"Job\nAlice,30,Engineer';
const result = Papa.parse(incorrectCSV, { header: true, dynamicTyping: true });

console.log(JSON.stringify(result.errors));
console.log(JSON.stringify(result.data));
Output

You should see a non-empty errors array describing the broken quote, and data typically empty when parsing cannot complete cleanly.

Browser: parse an uploaded file

html
<input type="file" id="csvFile" accept=".csv,text/csv" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.5.3/papaparse.min.js"></script>
<script>
  document.getElementById('csvFile').addEventListener('change', (event) => {
    const file = event.target.files[0];
    if (!file) return;
    Papa.parse(file, {
      header: true,
      complete: (results) => {
        console.log(results.data);
        if (results.errors.length) console.warn(results.errors);
      },
    });
  });
</script>

HTML5 File API (FileReader)

Let the user pick a file, read text with FileReader, then reuse the same string parsers as above (naive split or Papa).

javascript
document.getElementById('csvFileInput').addEventListener('change', (event) => {
  const selectedFile = event.target.files[0];
  if (!selectedFile) return;

  const reader = new FileReader();
  reader.onload = (e) => {
    const fileContent = /** @type {string} */ (e.target.result);
    const rows = fileContent.split(/\r?\n/).filter(Boolean);
    const array = rows.map((row) => row.split(','));
    console.log(array);
  };
  reader.onerror = () => console.error(reader.error);
  reader.readAsText(selectedFile);
});

For the same sample bytes as the manual string example, the console output matches the manual parsing JSON shown earlier.

See also FileReader on MDN.


Node.js: readline + fs (line by line)

readline with fs.createReadStream walks the file one line at a time, so memory stays bounded. Pair it with split(',') only when rows are simple (no embedded commas inside fields).

javascript
const fs = require('fs');
const readline = require('readline');

async function processFile(filePath) {
  const fileStream = fs.createReadStream(filePath);

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity,
  });

  const array = [];
  for await (const line of rl) {
    array.push(line.split(','));
  }
  return array;
}

processFile('example.csv')
  .then((data) => console.log(JSON.stringify(data)))
  .catch((err) => console.error(err));

If example.csv on disk matches:

plaintext
Name,Age,Job
Alice,30,Engineer
Bob,40,Doctor

then processFile logs one JSON string: the same header row plus two data rows as nested string arrays.


Node.js: csv-parser (row objects, streaming)

The csv-parser package turns each row into an object keyed by the header row and works well with pipe from a read stream.

bash
npm install csv-parser
javascript
const fs = require('fs');
const csv = require('csv-parser');

const parsedData = [];

fs.createReadStream('large-example.csv')
  .pipe(csv())
  .on('data', (row) => parsedData.push(row))
  .on('end', () => {
    console.log(JSON.stringify(parsedData));
  })
  .on('error', (err) => console.error(err.message));

Sample large-example.csv:

plaintext
ID,Name,Age,Occupation
1,Alice,30,Engineer
2,Bob,40,Doctor
3,Charlie,50,Teacher

When the stream finishes, you should see one JSON string: three row objects keyed by those headers (values remain strings unless you add conversion).


XMLHttpRequest (legacy)

Older codebases still use XMLHttpRequest. Prefer fetch in new code. The pattern is the same: read responseText, split rows, split fields, with the same caveats as manual parsing.


D3 (d3.csvParse for strings)

If you already ship D3 or d3-dsv, csvParse turns a CSV string into an array of objects (similar to Papa with header: true). d3.csv loads a URL instead; that is a different entry point.

Example with an ES module import (matches d3-dsv and modern D3 bundles):

javascript
import { csvParse } from 'd3-dsv';

const text = `Name,Age,Job
Alice,30,Engineer
Bob,40,Doctor`;

console.log(JSON.stringify(csvParse(text)));

You should see one JSON string: two row objects with string field values (D3 does not apply dynamicTyping unless you add it yourself).


CSV → JSON string → array

Sometimes you round-trip through JSON for APIs or storage (JSON in JS, nested JSON search). The pattern below builds objects, stringifies, then parses again to show a clean separation of steps.

javascript
const csv = `Name,Age,Job
Alice,30,Engineer
Bob,40,Doctor`;

const rows = csv.split('\n');
const headers = rows[0].split(',');
const array = rows.slice(1).map((row) => {
  const values = row.split(',');
  const obj = {};
  headers.forEach((header, index) => {
    obj[header] = values[index];
  });
  return obj;
});

const json = JSON.stringify(array);
const jsonArray = JSON.parse(json);

jsonArray.forEach((row) => {
  console.log(`Name: ${row.Name}, Age: ${row.Age}, Job: ${row.Job}`);
});
Output

You should see two console.log lines—one per person—with interpolated Name, Age, and Job.

Validate row width when you control the pipeline:

javascript
if (values.length !== headers.length) {
  console.error('Row has incorrect number of fields:', row);
}
Output

JavaScript array to CSV (export)

Papa.unparse turns an array of rows back into a CSV string (each row is an array of cell values):

javascript
const Papa = require('papaparse');
const rows = [
  ['Name', 'Age'],
  ['Alice', 30],
];
console.log(Papa.unparse(rows));
Output

You should see one multi-line string: a header row followed by Alice and 30, with a newline between rows.


Regex-based parsing

Regular expressions can match delimited cells, but they become brittle with nested quotes or multiline fields (see “regex vs state machine” discussions for CSV). One older pattern flattens every cell, then re-slices by a fixed column count:

javascript
const csvString =
  'Name,Age,"Address, Number",Job\nAlice,30,"123 St, Apt 4",Engineer';
const regex = /(".*?"|[^",\n]+)(?=\s*,|\s*\n|$)/g;
const array = [];
let m;
do {
  m = regex.exec(csvString);
  if (m) array.push(m[1].replace(/"/g, ''));
} while (m);

const perLine = 4;
const lines = [];
for (let i = 0; i < array.length; i += perLine) {
  lines.push(array.slice(i, i + perLine));
}

console.log(JSON.stringify(array));
console.log(JSON.stringify(lines));
Output

You should see two JSON lines: first a flat list of eight cell strings, then a pair of four-column rows grouped from that flat list.

Prefer Papa or d3.csvParse before investing in custom regex.


Summary

Turning javascript csv to array data is almost always a three-step story: read raw text, split into rows, then split or parse each row into fields. People ask whether they can use String.split alone for csv javascript work; it is fine for toy data, but real csv to javascript inputs often include quoted commas, inconsistent line endings, or a UTF-8 BOM, which is why libraries like Papa Parse or d3.csvParse exist and why teams reach for them before writing custom parsers.

In the browser you normally combine a file input or fetch with one of those parsers; in Node you might stream very large files with readline, csv-parser, or Papa’s streaming API so memory stays bounded. If you are deciding between a 2D string array and an array of objects, pick objects when the first row is a stable header row you want as keys, and pick raw arrays when you need maximum speed or irregular columns.


References


Frequently Asked Questions

1. How do I read a CSV file into an array in the browser?

Use a file input with FileReader.readAsText or await file.text(), then split and parse the string, or pass the File to Papa.parse. For remote CSV, fetch the URL then response.text() and parse; watch CORS.

2. Is csv.split newline then split by comma enough for javascript parse csv?

Often yes for simple exports with no quoted commas or embedded newlines. For Excel or user CSV, use Papa Parse or d3.csvParse so RFC-style quoting is handled.

3. How do I parse CSV from a URL?

Use fetch(url).then(r => r.text()) or Papa.parse with download true. Check response.ok and handle CORS for cross-origin hosts.

4. What about a UTF-8 BOM at the start of the file?

Strip U+FEFF from the string before treating the first line as headers, or configure your parser if it supports skipBOM style options.

5. In Node.js should I read the whole CSV or stream it?

fs.readFile is fine for small files. For large files use readline, csv-parser, or Papa streaming so you do not load the entire file into memory at once.

6. How do I export javascript array to csv?

Build rows as arrays of strings or objects and use Papa.unparse, or join cells with commas and rows with newlines yourself for trivial cases.

7. Can I still use jQuery to load CSV text?

Yes with $.ajax dataType text, but fetch or Papa cover the same need in modern code without jQuery.
Olorunfemi Akinlua

Boasting over five years of experience in JavaScript, specializing in technical content writing and UX design. With a keen focus on programming languages, he crafts compelling content and designs …