# parquet-wasm: reading and writing Parquet from the browser through Arrow

> parquet-wasm compiles the Rust parquet and arrow crates to WebAssembly so a browser can read a Parquet file into an Arrow table and write one back out, with streaming reads of remote files. The catch is manual memory management: every object holding data in Wasm memory has a free method, and some functions free your inputs for you.

**kylebarron/parquet-wasm** — Rust-based WebAssembly bindings to read and write Apache Parquet data

- Repository: https://github.com/kylebarron/parquet-wasm
- Website: https://kylebarron.dev/parquet-wasm/
- Stars: 674 · Forks: 34
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kylebarron-parquet-wasm

## Parquet and Arrow, compiled into a Wasm module you can call from JavaScript

parquet-wasm is a set of WebAssembly bindings that read and write the Apache Parquet format to and from Apache Arrow, built on the Rust parquet and arrow crates. The important design decision is stated in the README's opening lines: it is designed to be used alongside a JavaScript Arrow implementation, such as the canonical JS Arrow library. So the Wasm module is not a Parquet implementation in isolation. It converts between the on-disk columnar format and Arrow's in-memory table representation, and it is the JavaScript side that owns the Arrow table and hands it to whatever wants to read it, a chart, a dataframe library, or the browser's own analytics tooling. That split is what makes it useful in a web application, because the expensive part, decoding columnar data, happens in compiled code while the JavaScript layer stays in the ecosystem it already lives in. The Rust source is licensed MIT or Apache-2.0, and the crate requires Rust 1.88 with edition 2024.

## A sync API that needs the whole file, and an async one that does not

The library exposes two APIs and the difference is about memory pressure, not style. The sync API is simpler but requires fetching the entire Parquet buffer in advance, which the README calls often prohibitive. The sync functions are readParquet to read a file synchronously, readSchema to read an Arrow schema from a Parquet file, and writeParquet to write one. The async side has readParquetStream, which creates a ReadableStream that emits Arrow RecordBatches from a Parquet file, and a ParquetFile class for reading portions of a remote Parquet file. ParquetFile is constructed with fromUrl for a remote URL or fromFile for a File handle, and the README is explicit that when you are done with the class you need to call free to release any memory held by the instance itself. That is the pattern for the class of problem this library exists to solve: a hundred megabyte Parquet file over HTTP should not become a hundred megabyte buffer in the tab.

## Memory ownership is the thing to get right

The memory management section is the most consequential part of the README and deserves reading before any code is written. Objects such as Table, RecordBatch, ParquetFile and WriterProperties hold data in WebAssembly memory. That memory is reclaimed eventually when the JavaScript object is garbage collected, but you can force a cleanup by calling .free() on each object. The sharp edge is that some functions take ownership of their inputs and call free for you. writeParquet frees both the table and the writerProperties. transformParquetStream frees the writerProperties. And methods whose names start with into, such as Table.intoIPCStream() and Table.intoFFI(), free the object they are called on. Don't call any method on an object after it has been freed, or an error will be thrown. To write the same data twice, create a new Table. The generated types also support using declarations, which call free at the end of the block, but only declare an object with using if you won't pass it to one of the functions above, otherwise the automatic free throws.

## Three entry points, and the ESM one must be initialized by hand

There are three ways into the package. The esm entry point is the primary one, available as parquet-wasm, parquet-wasm/esm or parquet-wasm/esm/parquet_wasm.js, for use directly from the web as an ES module. The bundler entry point, parquet-wasm/bundler, is for bundlers such as Webpack. The node entry point, parquet-wasm/node, is for synchronous require in NodeJS. The ESM entry point carries a sharp requirement: you must manually initialize the WebAssembly module before using any APIs, or you get a TypeError about reading properties of undefined. The primary way to do that is to await the default export.

```js
import wasmInit, {readParquet} from "parquet-wasm";

await wasmInit();
```

Without a parameter this fetches a file named parquet_wasm_bg.wasm from the same location as the package, and you can pass a custom URL if you want to host the file yourself. There is also a synchronous initSync named export that takes the contents of the wasm file as an ArrayBuffer, though the README recommends the asynchronous route because WebAssembly.instantiateStreaming is the most efficient way to both download and initialize the module.

## Cargo features are how you shrink the bundle

Bundle size is the argument for the custom builds, and the Cargo feature flags are the mechanism. The default feature set is all_compressions, reader, writer and async, and the individual features break down further. Each compression codec is its own feature: brotli, gzip, snappy, zstd and lz4, with all_compressions turning on all of them. reader, writer and async are separate switches, debug enables the console_error_panic_hook for better panic messages in development, and full combines async, debug, all_compressions, reader and writer. The README gives the sizes to aim for. With read and write support and all compression codecs, the brotli-compressed Wasm bundle is 1.2 MB. A minimal read-only bundle without compression support can be as small as 456 KB brotli-compressed. The crate also depends on a small allocator, wee_alloc, which is about 1K in code size against roughly 10K for the default allocator, at the cost of being slower, with the source comment noting that if it only saves 10KB it might not be worth the slower allocation.

## Rust internals, typedoc docs and a vitest suite

The repository is a Rust crate with a JavaScript-facing package wrapped around it, and the tooling reflects that split. The Cargo.toml declares the crate type as both cdylib and rlib, so it can be used as a WebAssembly module and as a normal Rust library. The wasm-bindgen dependency carries a serde-serialize feature, and serde plus serde-wasm-bindgen handle the conversion of Rust structures into JavaScript values. On the JavaScript side, the package scripts build with a bash script, run biome for checking, formatting and linting, generate documentation with typedoc, serve it with a Python http server on port 8081, and publish it to gh-pages. Tests run under vitest against a TypeScript test file. The dev dependencies also include apache-arrow, arrow-js-ffi, benny for benchmarking, and fastify with @fastify/static, which suggests the test harness serves files locally so the Wasm module can be fetched the way it would be in a browser. There is a bench directory, a templates directory, a scripts directory and a DEVELOP.md.

## Conclusion

parquet-wasm fits a web application that needs to open columnar data in the browser, especially a dashboard that receives Parquet over the network, since the async API streams remote files instead of pulling the whole buffer into memory. It does not fit a codebase that wants implicit cleanup, because the ownership rules around free are sharp enough to cause errors if you get them wrong, and it does not fit a project that cannot afford a 1.2 MB compressed bundle. Verify first which features you actually need, because the Cargo feature flags let you cut the bundle down to a read-only build without compression support, and read the ownership rules before you pass objects between calls.

## FAQ

### How do I install parquet-wasm?

It is published to NPM, and the README gives yarn add parquet-wasm or npm install parquet-wasm. The default bundle includes read and write support and all compression codecs and is 1.2 MB brotli-compressed, and the custom build section explains how to produce a smaller one.

### What is the difference between the sync and async API in parquet-wasm?

The sync API is simpler but requires fetching the entire Parquet buffer in advance, which the README calls often prohibitive. The async side provides readParquetStream, which returns a ReadableStream of Arrow RecordBatches, and a ParquetFile class that reads portions of a remote file constructed with fromUrl or fromFile.

### How do I free memory when using parquet-wasm?

Objects such as Table, RecordBatch, ParquetFile and WriterProperties hold data in Wasm memory, and you can call .free() on each to release it immediately. Note that some functions free your inputs for you, writeParquet frees the table and writerProperties, and methods starting with into free the object they are called on. Using an object after it has been freed throws an error.

### Do I have to initialize the WebAssembly module before using parquet-wasm?

Yes, when using the esm bundles. You must manually initialize the module before any API call, otherwise you get a TypeError about reading properties of undefined. The primary way is to await the default export, and you can pass a custom URL to host the wasm file yourself.

## Sources

- [kylebarron/parquet-wasm on GitHub](https://github.com/kylebarron/parquet-wasm)
- [License: Apache-2.0](https://github.com/kylebarron/parquet-wasm/blob/main/LICENSE)
- [Project website](https://kylebarron.dev/parquet-wasm/)
- [README](https://github.com/kylebarron/parquet-wasm/blob/main/README.md)
- [Releases](https://github.com/kylebarron/parquet-wasm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kylebarron-parquet-wasm
