Regex for Splitting CSV Strings — Handling Quoted Fields Correctly
line.split(',') is the first thing every developer tries on CSV data, and it works right up until a field contains a comma inside quotes — then every column after it shifts by one. This guide shows the regex fix, its limits, and when to reach for a real CSV parsing library instead.
The Problem with a Plain Comma Split
const row = 'John,"Doe, Jr.",Mumbai,India';
row.split(',');
// ['John', '"Doe', ' Jr."', 'Mumbai', 'India']
// WRONG — the quoted "Doe, Jr." field got split into two columnsSplitting with a Lookahead-Aware Regex
The trick is to only split on a comma when it is followed by an even number of quote characters for the rest of the line — meaning it is outside any open quote:
const row = 'John,"Doe, Jr.",Mumbai,India';
const fields = row.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
console.log(fields);
// ['John', '"Doe, Jr."', 'Mumbai', 'India'] — correct!
// Strip the surrounding quotes afterward
const cleaned = fields.map((f) => f.replace(/^"|"$/g, ''));
// ['John', 'Doe, Jr.', 'Mumbai', 'India']The lookahead (?=(?:(?:[^"]*"){2})*[^"]*$) counts quote characters ahead of each comma in pairs — if there's an even number of quotes remaining, the comma is "outside" a quoted field and safe to split on.
Extracting Fields Directly with a Match Regex
An alternative approach matches each field directly instead of splitting, which is often easier to reason about:
function parseCsvLine(line) {
const fieldRegex = /(?:^|,)("(?:[^"]|"")*"|[^,]*)/g;
const fields = [];
let match;
while ((match = fieldRegex.exec(line)) !== null) {
if (match[0] === '' && match.index === line.length) break;
let value = match[1];
if (value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1).replace(/""/g, '"'); // unescape doubled quotes
}
fields.push(value);
}
return fields;
}
parseCsvLine('John,"Doe, Jr.",Mumbai,"Says ""hi""!"');
// ['John', 'Doe, Jr.', 'Mumbai', 'Says "hi"!']Where Regex-Based CSV Parsing Still Falls Short
- Embedded newlines — a quoted field can legally contain a line break, which breaks any line-by-line regex approach that splits on
\nfirst - Different encodings — UTF-8 BOM markers, non-UTF-8 files, and mixed line endings (CRLF vs LF) need dedicated handling
- Alternate delimiters — semicolon-delimited "CSV" (common in European locales) or tab-delimited TSV need the pattern rewritten
- Performance on large files — regex-per-line parsing of a multi-million-row file is far slower than a streaming parser built for the format
For any production import/export feature, use a library:
// Node.js — using csv-parse
const { parse } = require('csv-parse/sync');
const records = parse(csvString, {
columns: true,
skip_empty_lines: true,
});When the Regex Approach Is Fine
- You control the data source and know it never contains embedded newlines in fields
- You are doing a quick one-off script or a small admin tool, not a production import pipeline
- You need zero dependencies for a small serverless function with tight bundle size limits
Frequently Asked Questions
Because CSV fields can contain commas inside quoted values, such as "Doe, John". A naive split(",") breaks that single field into two, misaligning every column after it.
A common pattern is ,(?=(?:(?:[^"]*"){2})*[^"]*$), a comma split that uses a lookahead to only split on commas outside an even number of quotes, meaning commas inside quoted fields are preserved.
Use a dedicated library such as PapaParse (browser/Node.js) or csv-parse (Node.js) for production. They correctly handle escaped quotes, embedded newlines inside quoted fields, different delimiters, and encoding issues that a hand-written regex will not.