csv

← All adapters

csv(options?: CsvTransformerOptions): Transformer   // no path: parse a CSV string in the body
csv(options: CsvFileOptions & { chunked: true }): Source<CsvRow>
csv(options: CsvFileOptions & { mode: 'read' }): CsvReadAdapter
csv(options: CsvFileOptions): CsvAdapter   // Source<CsvData> & Destination<unknown, void>

Read and write CSV files with automatic parsing/formatting. Requires papaparse as a peer dependency.

bun add papaparse

Transformer mode (parse a CSV string already in the body):

// Parse a CSV string (e.g. an http() response body) into rows
.transform(csv())

// Pluck the string and write the rows to a sub-field
.transform(csv({
  from: (b) => b.body,
  to: (b, rows) => ({ ...b, rows })
}))

Source mode (read CSV files):

// Read CSV with headers
.from(csv({ path: './data.csv', header: true }))
// Emits array of objects: [{ name: 'Alice', age: '30' }, ...]

// Read CSV without headers
.from(csv({ path: './data.csv', header: false }))
// Emits array of arrays: [['Alice', '30'], ['Bob', '25'], ...]

// Custom delimiter and encoding
.from(csv({
  path: './data.csv',
  delimiter: ';',
  encoding: 'latin1',
  header: true
}))

Read mid-route (read + parse a CSV file partway through a route): In read mode the adapter is also a destination whose send reads and parses the file and returns the rows, so .enrich() / .to() can pull them in, the same way an HTTP GET returns a body. Read-as-destination accepts dynamic (function) paths. Parse failures throw and surface through the pipeline (the onParseError lifecycle controls apply to source mode only).

// Enrich the body with the parsed rows, keeping the existing fields
.enrich(
  csv({ path: './catalogue.csv', mode: 'read' }),
  only((rows) => rows, 'rows'),
)

// Replace the body with the parsed rows
.to(csv({ path: './data.csv', mode: 'read' }))

Destination mode (write CSV files):

// Write array of objects to CSV
.to(csv({
  path: './output.csv',
  header: true
}))
// Automatically includes headers from object keys

// Write to tab-separated file
.to(csv({
  path: './data.tsv',
  delimiter: '\t',
  header: true
}))

// Dynamic paths with directory creation
.to(csv({
  path: (exchange) => `./reports/${exchange.body.reportDate}.csv`,
  createDirs: true,
  header: true
}))

// Append to existing CSV (skips header if file exists)
.to(csv({
  path: './log.csv',
  mode: 'append',
  header: true
}))

// Delete a CSV file (idempotent: an already-absent path is a no-op)
.to(csv({ path: (ex) => ex.body.processedPath, mode: 'delete' }))

Transformer Options (when no path provided):

OptionTypeDefaultDescription
from(body) => stringUses body or body.bodyExtract the CSV string from the exchange
to(body, rows) => RReplaces bodyWhere to put the parsed rows
header / delimiter / quoteChar / skipEmptyLinesSame parsing options as below

File Options (when path is provided):

OptionTypeDefaultDescription
pathstring | (exchange) => stringRequiredFile path (static or dynamic)
headerbooleantrueUse first row as headers (source), include headers (destination)
delimiterstring','Field separator
quoteCharstring'"'Quote character
skipEmptyLinesbooleantrueSkip empty lines during parsing
encodingBufferEncoding'utf-8'Text encoding
mode'read' | 'write' | 'append' | 'delete''read' for source, 'write' for destinationFile operation mode (read returns parsed rows mid-route; delete removes the file, idempotently)
createDirsbooleanfalseCreate parent directories (destination only)
chunkedbooleanfalseEmit one exchange per row instead of entire array (source only)
onParseError'fail' | 'abort' | 'drop''fail'How to handle a row parse failure (source only). See parse error handling.

Behavior:

  • Source (default): Emits entire CSV as array of records (objects if header: true, arrays if header: false)
  • Source (chunked: true): Emits one exchange per row with CsvHeaders.ROW (1-based row number) and CsvHeaders.PATH headers. Returns Source only (no Destination). With onParseError: 'fail' (default) malformed rows are routed through the route's .error() handler and the stream continues; 'abort' reverts to fail-fast on the first bad row; 'drop' emits exchange:dropped with reason: 'parse-failed'.
  • Destination: Writes exchange body (array of objects/arrays) as CSV. For mode: 'append', skips header row if file exists
// Per-row emission
.from(csv({ path: './big.csv', chunked: true }))

Peer dependency: Requires papaparse to be installed separately.

Exported symbols: CsvHeaders (the header key object used above, e.g. CsvHeaders.ROW / CsvHeaders.PATH); types CsvAdapter, CsvReadAdapter, CsvOptions, CsvTransformerOptions, CsvFileOptions, CsvRow, CsvData