PapaParse Reference

CSV parsing and generation library. Use PapaParse to parse CSV strings into structured data and convert data back to CSV for export.

Import

import Papa from "papaparse";

Core Concepts

  • Papa.parse() parses CSV text into arrays or objects
  • Papa.unparse() converts arrays/objects back to CSV text
  • header: true treats the first row as field names and returns objects instead of arrays
  • dynamicTyping: true auto-converts numbers and booleans from strings
  • Combine with fetch() to load CSV app assets, or with useFileUpload for user-uploaded CSV files

Parsing CSV Strings

Basic Parse (Arrays)

const csv = "Name,Age,City\nAlice,30,NYC\nBob,25,LA";

const result = Papa.parse(csv);
// result.data = [
//   ["Name", "Age", "City"],
//   ["Alice", "30", "NYC"],
//   ["Bob", "25", "LA"]
// ]

Parse with Headers (Objects)

const csv = "Name,Age,City\nAlice,30,NYC\nBob,25,LA";

const result = Papa.parse(csv, {
  header: true,
  skipEmptyLines: true,
});
// result.data = [
//   { Name: "Alice", Age: "30", City: "NYC" },
//   { Name: "Bob", Age: "25", City: "LA" }
// ]
// result.meta.fields = ["Name", "Age", "City"]

Parse with Dynamic Typing

const csv = "Name,Age,Active\nAlice,30,true\nBob,25,false";

const result = Papa.parse(csv, {
  header: true,
  dynamicTyping: true,
  skipEmptyLines: true,
});
// result.data = [
//   { Name: "Alice", Age: 30, Active: true },
//   { Name: "Bob", Age: 25, Active: false }
// ]

Transform Values During Parse

const result = Papa.parse(csv, {
  header: true,
  dynamicTyping: true,
  skipEmptyLines: true,
  transformHeader: (header) => header.trim().toLowerCase().replace(/\s+/g, "_"),
  transform: (value) => value.trim(),
});

Parse Config Options

OptionTypeDefaultDescription
headerbooleanfalseFirst row is field names; rows become objects keyed by field name
dynamicTypingbooleanfalseAuto-convert numeric and boolean strings to their types
skipEmptyLinesboolean | "greedy"falseSkip empty lines; "greedy" also skips whitespace-only lines
delimiterstringauto-detectDelimiting character (leave blank to auto-detect)
commentsstringfalseString that indicates a comment line (e.g. "#")
transformHeaderfunctionundefinedTransform each header name; receives (header, index)
transformfunctionundefinedTransform each cell value; receives (value, columnNameOrIndex)
previewnumber0If > 0, only parse that many rows
skipFirstNLinesnumber0Skip first N lines before parsing

Parse Result Object

const result = Papa.parse(csv, { header: true });

result.data    // Array of parsed rows (arrays or objects)
result.errors  // Array of parse errors
result.meta    // { delimiter, linebreak, fields, aborted, truncated }

Error Structure

// Each error in result.errors:
{
  type: "Quotes" | "Delimiter" | "FieldMismatch",
  code: "MissingQuotes" | "InvalidQuotes" | "UndetectableDelimiter" | "TooFewFields" | "TooManyFields",
  message: "Human-readable details",
  row?: number,   // Row index where error occurred (optional)
  index?: number, // Character index within the row (optional)
}

Converting Data to CSV

Unparse Arrays

const csv = Papa.unparse([
  ["Name", "Age", "City"],
  ["Alice", "30", "NYC"],
  ["Bob", "25", "LA"],
]);
// "Name,Age,City\r\nAlice,30,NYC\r\nBob,25,LA"

Unparse Objects (Auto Header Row)

const csv = Papa.unparse([
  { Name: "Alice", Age: 30, City: "NYC" },
  { Name: "Bob", Age: 25, City: "LA" },
]);
// "Name,Age,City\r\nAlice,30,NYC\r\nBob,25,LA"

Unparse with Explicit Fields

const csv = Papa.unparse({
  fields: ["Name", "City"],
  data: [
    ["Alice", "NYC"],
    ["Bob", "LA"],
  ],
});

Unparse Config Options

OptionTypeDefaultDescription
headerbooleantrueInclude header row (ignored for array of arrays)
delimiterstring","Delimiting character
quotesboolean | boolean[] | functionfalseForce-quote all fields, per column array, or (value, colIndex) => boolean
newlinestring"\r\n"Newline sequence
skipEmptyLinesboolean | "greedy"falseSkip empty lines; "greedy" also skips whitespace-only lines
columnsstring[]undefinedManually specify keys to include (for array of objects)

Example: Load and Display CSV App Asset

import * as React from "react";
import Papa from "papaparse";

const CSV_ASSET_URL = "https://...blob-url-from-assets...";

type Row = Record<string, string>;

export default function App() {
  const [rows, setRows] = React.useState<Row[]>([]);
  const [columns, setColumns] = React.useState<string[]>([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    const loadData = async () => {
      const response = await fetch(CSV_ASSET_URL);
      const text = await response.text();
      const result = Papa.parse<Row>(text, {
        header: true,
        dynamicTyping: true,
        skipEmptyLines: true,
      });
      setColumns(result.meta.fields ?? []);
      setRows(result.data);
      setLoading(false);
    };
    loadData();
  }, []);

  if (loading) return <div className="flex justify-center p-8"><span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" /></div>;

  return (
    <div>
      <table>
        <thead>
          <tr>
            {columns.map((col) => (
              <th key={col}>{col}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row, i) => (
            <tr key={i}>
              {columns.map((col) => (
                <td key={col}>{String(row[col] ?? "")}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Example: Upload CSV, Parse, and Persist

import * as React from "react";
import Papa from "papaparse";
import { Download } from "lucide-react";

type Expense = { date: string; category: string; amount: number; note: string };

export default function App() {
  const [expenses] = usePersistentItem<Expense[]>("expenses", []);
  const { download } = useDownload();

  const handleExport = () => {
    const csv = Papa.unparse(expenses);
    download(csv, "expenses.csv");
  };

  return (
    <div>
      <button onClick={handleExport} disabled={expenses.length === 0}>
        <Download className="w-4 h-4" />
        Export {expenses.length} expenses
      </button>
    </div>
  );
}

Best Practices

  1. Always use header: true for CSV with a header row — returns objects instead of index-based arrays
  2. Use dynamicTyping: true to auto-convert numbers and booleans — avoids manual parseInt/parseFloat
  3. Use skipEmptyLines: true to avoid empty trailing rows from spreadsheet exports
  4. Check result.errors after parsing — errors don't necessarily mean failure, but warn about data issues
  5. Use transformHeader to normalize headers — trim whitespace, lowercase, replace spaces with underscores
  6. Use Papa.unparse() for CSV export instead of manual string concatenation — handles quoting, escaping, and special characters correctly
  7. Type your parsed data — use Papa.parse<MyType>(text, config) for TypeScript type safety