Library / SDK
dashbitco/nimble_csv avatar
dashbitco/nimble_csv

NimbleCSV parses rows and leaves casting, headers and dialect to you

A simple and fast CSV parsing and dumping library for Elixir

824 stars60 forksElixirLicense varies

At a glance

What is it?
NimbleCSV is a Dashbit library for Elixir that parses and dumps CSV using custom parsers built from binary patterns, with eager and streaming modes and an RFC4180 implementation included. It is Apache-2.0 licensed, published on Hex as nimble_csv, and was last pushed on 2026-09-19.
Who is it for?
NimbleCSV fits an Elixir project that already works with rows as lists of binaries and wants parsing that streams, with a custom dialect defined in a few lines, and that does not want a library deciding how values are cast or how headers map to fields. It is the wrong choice when you want typed structs straight out of the parser, since the README says explicitly that data casting and header management are left for you to build on top.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 34 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Rows as lists of binaries, and nothing else

NimbleCSV is a simple and fast CSV parsing and dumping library for Elixir, published by Dashbit with documentation at hexdocs.pm/nimble_csv. The homepage of the project is that documentation site, the default branch is master, the repository is not archived, and the last push was on 2026-09-19.

The design decision that shapes everything is what the library does not do. The README says the library was designed to be simple and compose well, and gives the example: instead of trying to tackle how data is cast or how headers are managed, it allows developers to build on top of the parsed results.

So a parsed row is a list of strings. Nothing is converted to an integer, a date or a map, and there is no notion of a header row that the parser understands. What you get is the row, and everything else is a decision you make afterwards.

That is a deliberate trade, and it is the right trade for a library that has to be fast. Converting values is the part of CSV handling that varies most between projects, and pushing it out of the parser means the parsing loop itself stays small enough to be expressed as binary patterns.

The repository layout is small and conventional for an Elixir library: lib/ for the code, test/ for the tests, bench/ for benchmarks, mix.exs and mix.lock for the project, a CHANGELOG.md, a .formatter.exs, and a CI workflow in .github/.

Custom parsers are binary patterns, not options

The performance story in the README is tied directly to the mechanism. The library is described as extremely fast, and its performance comes from allowing developers to define custom parsers and dumpers which rely on binary patterns for efficiency.

The example is short.

elixir
# Define the parser (this is equivalent to calling
# defmodule and should be done at the top of a file)
NimbleCSV.define(MyParser, separator: "\t", escape: "\"")

# Parse the data
MyParser.parse_string("name\tage\njohn\t27")
#=> [["john","27"]]

NimbleCSV.define is a macro, and the comment notes the equivalence to calling defmodule, which is why the definition belongs at the top of a file. The separator here is a tab and the escape character is a double quote, so this parser handles tab separated data with quoted fields.

What is happening under the hood is pattern matching on binaries rather than a general purpose tokenizer that has to handle any dialect. A dialect specific to your data can be compiled into a pattern that the runtime matches directly, and a dialect with more separators or escaping rules can be expressed in a way the general case cannot.

The trade-off is that you are writing a parser, in a sense, when you need a dialect the library does not ship. The README frames this as the reason the library is fast, and for a data pipeline that reads the same format every day it is a good bargain. For a tool that must accept arbitrary CSV from unknown sources, the flexibility is a burden rather than a feature.

Streaming matters when the file does not fit in memory

NimbleCSV provides both eager and lazy, streaming, parsing as well as data dumping, and the streaming example is the one that shows how the library is meant to compose with the rest of Elixir.

elixir
# Lazily parses a file stream
"path/to/file"
|> File.stream!
|> MyParser.parse_stream()
|> Stream.map(fn [name, age] ->
  %{name: :binary.copy(name), age: String.to_integer(age)}
end)
|> Stream.run()

The pipeline is the point. File.stream! gives a lazy stream of the file, the parser turns it into a lazy stream of rows, and the map turns each row into the shape the application wants. Nothing is loaded in full, and the casting that the library deliberately refuses to do happens in the Stream.map, where it is visible in the code that owns the data.

For a large export, a nightly job, or anything that has to survive a file larger than memory, this is the difference between a working pipeline and an out of memory failure. It also means the work happens in chunks, which is the property that makes the binary pattern approach worthwhile in the first place.

The eager mode exists for the other case, where the whole input is a string or a small file and the result is needed at once, and the library also does the reverse operation, dumping rows back out to CSV.

RFC4180 is included, and aliasing it is the intended path

By default the library ships with NimbleCSV.RFC4180, which the README describes as the most common implementation of CSV parsing and dumping available, using comma as separator and double quote as escape.

elixir
alias NimbleCSV.RFC4180, as: CSV
CSV.parse_string("name,age\njohn,27")
[["john","27"]]

The alias is the point. If your project uses ordinary comma separated CSV, the intended usage is to alias the module to CSV and call it, rather than to define your own parser, and the README says as much: simply alias it to CSV and enjoy.

There is a subtlety worth checking before you commit to it. The parse result shown in the README is a list with one row of two strings, even though the input string contains a header line, name,age. That indicates the header row is not consumed automatically, which is consistent with the stated position that header management is the developer's problem. Whether the first row is a header is a question about your data, not about the parser.

Dumping is available on the same module, so the round trip, parse then dump, uses the same dialect definition, and a project that later needs a different separator can switch to a custom parser without changing anything else in the pipeline.

Adding it to a Mix project

Installation is the standard Hex dependency in mix.exs, and the README gives the version constraint it expects.

elixir
def deps do
  [
    {:nimble_csv, "~> 1.1"}
  ]
end

The constraint of about 1.1 is the important detail, because the repository's default branch is master and the project publishes no GitHub releases, so a dependency on the branch would pull unreviewed code into a production build. Pinning to a published Hex version is the predictable option.

The package is part of a family. The README lists all the nimble libraries by Dashbit: NimbleCSV for simple and fast CSV parsing, NimbleOptions for validating and documenting high level options, NimbleOwnership for resource ownership tracking, NimbleParsec for parser combinators, NimblePool for a resource pool implementation, NimblePublisher for a filesystem based publishing engine with Markdown support and code highlighting, and NimbleTOTP for time based one time passwords.

The copyright lines give the history: Copyright 2016 Plataformatec and Copyright 2020 Dashbit, which matches a library that started under one company and is now maintained by another. The licence is the Apache License, Version 2.0, with the standard text in the README, and the per file licence headers and the CHANGELOG are in the repository.

Where NimbleCSV is the wrong tool

Three cases argue against it.

The first is when you want typed rows out of the parser. The library states that it does not handle casting or headers, so a project whose data flow expects structs will add a conversion layer on top of every read. At that point the library is providing the tokenising and you are providing the parts that decide what the data means, and the same reasoning applies to a tool that has to guess whether a column is an integer, a decimal or an identifier.

The second is when the input dialect is unknown. The performance advantage comes from committing to a dialect at compile time through binary patterns, which is exactly what you cannot do for a file uploaded by a stranger with any of the usual variations. The RFC4180 module covers the common case, and anything else means writing a parser, which is a bigger job than using a general purpose CSV reader that already handles the edge cases.

The third is when the volume is small and the file is simple. A file of a few hundred rows with a fixed comma layout can be handled with the standard library's split on newlines and a trim, and a dependency with a compiled parser and its own dialect definitions is not worth the indirection.

Within its own shape, though, the library is well judged. Fast parsing, lazy streams, a dialect you can define in three lines, and a deliberate refusal to make decisions about your data.

Editorial conclusion

NimbleCSV fits an Elixir project that already works with rows as lists of binaries and wants parsing that streams, with a custom dialect defined in a few lines, and that does not want a library deciding how values are cast or how headers map to fields. It is the wrong choice when you want typed structs straight out of the parser, since the README says explicitly that data casting and header management are left for you to build on top. Before adopting it, read the RFC4180 module and decide whether the default comma and double quote dialect is enough, and add nimble_csv with a version constraint in mix.exs rather than depending on the master branch.

Frequently asked questions

How do I add NimbleCSV to an Elixir project?

Add the dependency in mix.exs with {:nimble_csv, "~> 1.1"}. The package is published on Hex and documented at hexdocs.pm/nimble_csv, and the repository publishes no GitHub releases, so a version constraint is the predictable choice.

How do I parse a tab separated file with NimbleCSV?

Define a parser with NimbleCSV.define, passing the separator and escape character, for example NimbleCSV.define(MyParser, separator: "\t", escape: "\""). The macro is equivalent to calling defmodule and should be placed at the top of a file.

Does NimbleCSV stream large files?

Yes. The library provides both eager and lazy streaming parsing, and the README example pipes File.stream! through MyParser.parse_stream() and then a Stream.map, so a file larger than memory can be processed chunk by chunk.

Does NimbleCSV convert values into numbers or dates?

No. The README says the library deliberately does not tackle how data is cast or how headers are managed, and instead lets developers build on top of the parsed results, which are lists of strings.

What license is NimbleCSV released under?

The README states the Apache License, Version 2.0, with copyright lines for 2016 Plataformatec and 2020 Dashbit. The project also publishes a CHANGELOG.md and includes a CI workflow for its test suite.

Official sources

  1. dashbitco/nimble_csv on GitHub
  2. Issues
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dashbitco-nimble-csv.svg)](https://hysenlabs.com/projects/dashbitco-nimble-csv)

Was this useful?