pub version pub points pub likes GitHub stars GitHub forks GitHub issues CI status Last commit License: MIT Dart

CSV Library for Dart & Flutter

csv_plus is a fast, complete, zero-dependency Dart library for parsing, encoding, streaming, querying, and validating CSV data. It works in plain Dart and in Flutter apps, on the VM, Web (JS & WASM), and mobile. csv_plus reads and writes RFC 4180 CSV with automatic type inference, a DataFrame-style table layer, schema validation, and constant-memory streaming, and it is the fastest general-purpose CSV package for Dart on every workload we measure.

Find this useful? Star it on GitHub and 👍 like it on pub.dev. Stars and likes help other Dart & Flutter developers find a maintained, full-featured CSV library.

Overview

csv_plus parses and generates comma-separated-values (CSV) text, along with tab-separated (TSV), pipe-delimited, and custom-delimiter formats. It decodes CSV into typed Dart values, encodes rows back to RFC 4180 output with correct quoting, streams large files with constant memory, and offers a table layer for filtering, sorting, grouping, aggregating, and validating tabular data. In short, it is a complete CSV serialization and deserialization toolkit for data processing and file handling in Dart and Flutter.

What you can do with it:

  • Decode CSV strings and files into typed rows (int, double, bool, String, null), or into a queryable table.
  • Encode rows, maps, and tables back to CSV, TSV, pipe-delimited, or Excel-flavored output with automatic quoting.
  • Stream very large CSV files row by row with a chunked, backpressure-aware transformer that never buffers the whole input.
  • Query, sort, group, aggregate, transform, and schema-validate tabular data with a DataFrame-style API.

Performance

csv_plus is built for throughput: a byte-level (codeUnits) batch parser with no regex and no string allocation in the hot loop, first-byte type detection, and a per-call StringBuffer encoder. Type inference is guarded so speed never costs correctness.

It is the fastest general-purpose CSV package for Dart on every workload, on both JIT and AOT. The numbers below are a median of 5 runs against csv 8.0.0 and serial_csv 0.5.2, on 200k rows x 10 cols plain (14.3 MB) and 100k x 10 quote-heavy (18.4 MB), on the same machine:

Workload (JIT) csv 8.0.0 serial_csv csv_plus
Decode, strings 181.2 ms 325.8 ms (own fmt) 94.0 ms
Decode, typed 224.9 ms 111.7 ms (own fmt) 96.8 ms
Decode, quote-heavy 133.7 ms n/a 77.6 ms
Encode, typed rows 153.2 ms 141.8 ms 125.2 ms
decodeWithHeaders 178.5 ms n/a 95.4 ms

serial_csv reads and writes only its own strict format rather than general RFC 4180 CSV, so its decode rows (marked own fmt) are not directly comparable. The full tables with AOT numbers, the seeded data generators, and an edge-case comparison battery live in benchmark/compare/. Timings vary by hardware, so reproduce them on your own machine:

cd benchmark/compare && dart pub get && dart run bench.dart

Table of contents

Key features

Everything you need to read, write, stream, and analyze CSV, on every Dart & Flutter platform.

📥 Decoding
  • Typed decode with automatic int / double / bool / null / String inference
  • Data-loss guards: 007, +1, whitespace, and 16+ digit ids stay text; quoted fields are never inferred
  • String-only, integer, double, and boolean decoders that throw on bad input instead of inventing values
  • Lenient (decodeFlexible) mode: trims whitespace and recovers unmatched quotes
  • Header-aware rows (CsvRow) with row['name'] and row[0] access
  • Delimiter auto-detection, BOM handling, and the Excel sep= hint
📤 Encoding
  • RFC 4180 output with automatic, correct quoting
  • Three quote modes: only-when-necessary, always, and strings-only
  • Encode rows, maps, uniform-typed grids, and tables
  • Custom delimiter, quote, escape, and line-ending configuration
  • Optional UTF-8 BOM for Excel compatibility
🌊 Streaming
  • Chunked StreamTransformer for constant-memory decode and encode
  • Real backpressure: a slow consumer never buffers the whole file
  • Correct across chunk boundaries that split mid-field, mid-escape, mid-CRLF, or mid-delimiter
  • bindBytes decodes and encodes UTF-8 byte streams directly
📊 Table, query & transform
  • CsvTable: a 2D structure with headers, 50+ methods
  • Filter, sort (stable), take, skip, distinct, and range
  • Aggregate: sum, avg, min, max, count, and groupBy
  • Add, remove, rename, reorder, and transform columns
  • Schema validation: column types, nullability, patterns, and custom validators
🛡️ Reliability & platform
  • One documented parsing semantics across batch and streaming, enforced by a conformance suite
  • Optional strict mode: throws CsvParseException with row and column on malformed input
  • Typed exceptions: CsvException and subtypes
  • Zero dependencies, pure Dart: VM, Web (dart2js + wasm), and Flutter mobile
  • dart:io isolated behind a separate import so the core works everywhere

Limitations

  • ❌ Comment-line skipping (#-prefixed rows)
  • ❌ Row windowing (skipRows / maxRows)
  • ❌ Per-column type coercion on decode (schemas validate, they do not coerce)

Roadmap

What ships next is driven by user requests on the issue tracker:

  • ⬜ Comment-line skipping (#-prefixed rows)
  • ⬜ Row windowing (skipRows / maxRows)
  • ⬜ Per-column type coercion driven by CsvSchema

Shipped milestones are in the changelog.

Error handling

Malformed-but-openable CSV degrades gracefully by default: text after a closing quote is appended to the field (Excel behavior), and an unterminated quote consumes the rest of the input. Pass strict: true to turn those into a typed, catchable CsvParseException that carries the row, column, and offset:

try {
  final rows = CsvCodec(CsvConfig(strict: true)).decode('"a"x,b');
} on CsvParseException catch (e) {
  print('Parse error at row ${e.row}, column ${e.column}: ${e.message}');
}

The typed decoders (decodeIntegers, decodeDoubles, decodeBooleans) throw CsvParseException on a cell they cannot convert rather than inventing a 0 or false. Schema violations throw CsvValidationException.

Example

A complete, runnable set of samples lives in the example/ directory (basic, table, streaming, file IO, and advanced). Clone the repository and run them, or copy any snippet from Getting started below.

Installation

dart pub add csv_plus
# or, in a Flutter app:
flutter pub add csv_plus

Then import it:

import 'package:csv_plus/csv_plus.dart';

Getting started

Encode and decode

final codec = CsvCodec();

// Encode.
final csv = codec.encode([
  ['name', 'age', 'score'],
  ['Alice', 30, 95.5],
  ['Bob', 25, 88.0],
]);

// Decode: types are inferred automatically.
final rows = codec.decode(csv);
// rows[1] == ['Alice', 30, 95.5]  (String, int, double)

Header-aware rows

final people = codec.decodeWithHeaders(csv);
print(people.first['name']); // Alice
print(people.first['age']);  // 30  (int, not String)

Type inference and typed decoders

// Inference is guarded so identifier-like data is not corrupted.
codec.decode('id,qty\n007,3');
// ['id', 'qty'], ['007', 3]  (007 stays a String; 3 becomes an int)

// Or force a whole grid to one type. These throw on a bad cell instead of
// inventing a value; pass emptyAs to fill blanks.
codec.decodeStrings(csv);            // List<List<String>>
codec.decodeIntegers('1,2\n3,4');    // List<List<int>>
codec.decodeDoubles('1.5,2.5');      // List<List<double>>
codec.decodeBooleans('true,0');      // List<List<bool>>  (true/false/1/0)
codec.decodeFlexible('  a , b ');    // lenient: trims, recovers bad quotes

Query and transform with CsvTable

final table = CsvTable.parse('name,age,city\nAlice,30,NYC\nBob,25,LA\nEve,35,NYC');

// Filter (returns a new table).
final adults = table.where((row) => (row['age'] as int) >= 30);

// Sort in place (stable).
table.sortBy('age');

// ...or get a sorted copy without touching the source.
final byAge = table.sortedBy('age');

// Export.
print(table.toCsv());
print(table.toFormattedString()); // pretty-printed aligned table

Aggregate and group

print(table.avg('age'));   // 30.0
print(table.sum('age'));   // 90
print(table.max('age'));   // 35

// Group rows by a column value into sub-tables.
final byCity = table.groupBy('city'); // {NYC: CsvTable, LA: CsvTable}

Stream large files

import 'package:csv_plus/io.dart';

// Constant memory, any file size.
await for (final row in CsvFile.stream('huge.csv')) {
  process(row);
}

Any string or byte stream works, with backpressure handled for you:

final rows = codec.decoder.bindBytes(byteStream); // Stream<List<int>>

Read and write files

import 'package:csv_plus/io.dart';

final table = await CsvFile.read('data.csv');
await CsvFile.write('out.csv', table);
await CsvFile.append('out.csv', [['Zoe', 41]]);

Configuration and presets

final excel = CsvCodec.excel(); // ';' delimiter + UTF-8 BOM
final tsv = CsvCodec.tsv();      // tab-separated
final pipe = CsvCodec.pipe();    // pipe-separated

// Or configure fully.
final custom = CsvCodec(CsvConfig(
  fieldDelimiter: '::',
  quoteMode: QuoteMode.always,
  skipEmptyLines: true,
));

Strict mode

// Throw on structurally malformed input instead of recovering.
final strict = CsvCodec(CsvConfig(strict: true));
strict.decode('"unterminated'); // throws CsvParseException

Schema validation

final schema = CsvSchema(columns: [
  CsvColumnDef(name: 'email', type: String, required: true, pattern: r'@'),
  CsvColumnDef(name: 'age', type: int, nullable: false),
]);

final errors = table.validate(schema);   // List<CsvValidationException>
final ok = table.conformsTo(schema);      // bool

Maps and two-column CSV

codec.encodeMap({'host': 'localhost', 'port': 8080});
codec.decodeMap('host,localhost\nport,8080'); // {host: localhost, port: 8080}

dart:convert integration

final adapter = codec.asCodec(); // Codec<List<List<dynamic>>, String>
final rows = adapter.decode('a,b\n1,2');
final piped = adapter.fuse(utf8); // fuse with other codecs

csv_plus vs csv

csv_plus and the csv package cover similar ground; csv_plus adds speed, a table layer, and stricter correctness.

csv_plus csv
Decode speed (typed, JIT) 97 ms 225 ms
Parsing semantics One truth across batch & streaming, conformance-tested Batch and streaming
Type inference Guarded (007, +1, big ids stay text) Coerces (may corrupt ids)
Table / query / schema layer Yes No
Streaming backpressure Yes Basic
Dependencies Zero Zero

Numbers are from the reproducible benchmark above.

FAQ

Is csv_plus a drop-in for the csv package? No, the APIs differ, but the concepts map directly (codec, typed decode, headers, streaming). Most migrations are a small, mechanical change.

Which platforms are supported? Dart VM, Web (both JavaScript and WebAssembly), and mobile (Android & iOS) via Flutter, plus desktop. It is pure Dart with no dart:io in the core path.

Does it handle large files without running out of memory? Yes. The streaming decoder and encoder process input in chunks with real backpressure, so memory stays constant regardless of file size.

Will type inference corrupt my ids or codes? No. Values with leading zeros, a leading plus, surrounding whitespace, or more than 15 digits stay strings, and quoted fields are never inferred, on every platform including the web.

Does it support TSV, pipe-delimited, and Excel CSV? Yes, via CsvCodec.tsv(), CsvCodec.pipe(), CsvCodec.excel(), or a custom CsvConfig with any single or multi-character delimiter.

Support and feedback

  • Found a bug or want a feature? Open an issue on the issue tracker.
  • Questions and ideas are welcome via GitHub Discussions.
  • Pull requests are welcome; see the repository for contribution guidelines.

About

csv_plus is an open-source, MIT-licensed, zero-dependency CSV library for Dart and Flutter, built around a byte-level parser and a chunked streaming transformer for speed and low memory on large files.

csv_plus is created and owned by Nurullah Al Masum.

Contributors

csv_plus grows with its community; every contributor is listed here:

csv_plus contributors

Want to help? Pull requests are welcome; see Support and feedback.

Libraries

codec
High-level codec facade for CSV encoding and decoding.
core
Core types and configuration for csv_plus.
csv_plus
Complete, high-performance CSV package for Dart.
decoder
CSV decoding: batch, streaming, and auto-detection.
encoder
CSV encoding: batch and streaming.
io
File I/O for CSV data (dart:io).
query
Filtering and sorting extensions for CsvTable.
table
2D table data structure for CSV data.
transform
Column manipulation and aggregation extensions for CsvTable.