# pbf

> a low-level, lightweight protocol buffers implementation in JavaScript

Latest version **5.1.2** (published 2026-07-09) · BSD-3-Clause license · 0 weekly downloads

## Install

```sh
npm install pbf
pnpm add pbf
yarn add pbf
bun add pbf
```

Provides the command `pbf`.

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 5.1.2 |
| Published | 2026-07-09 |
| First published | 2014-01-14 |
| Weekly downloads | 0 |
| License | BSD-3-Clause |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 1 |
| Unpacked size | 88 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 880 |
| Author | Konstantin Kaefer |
| Maintainers | mapbox-npm-01, mapbox-npm-02, mapbox-npm-07, mapbox-npm-03, mapbox-npm-04, mapbox-npm-09, mapbox-npm-05, mapbox-npm-06, mapbox-npm-08, mapbox-npm-advanced-actions, mapbox-npm-ci, mapbox-npm, mapbox-admin, mapbox-machine-user, mbx-npm-ci-staging, mbx-npm-ci-production, mbx-npm-01-production, mbx-npm-02-production, mbx-npm-03-production, mbx-npm-04-production, mbx-npm-05-production, mbx-npm-06-production, mbx-npm-07-production, mbx-npm-08-production, mbx-npm-09-production, mbx-npm-02-staging, mbx-npm-advanced-actions-staging, mbx-npm-advanced-actions-production |
| Keywords | protocol, buffer, pbf, protobuf, binary, format, serialization, encoder, decoder |

## Links

- npm: https://www.npmjs.com/package/pbf
- Repository: https://github.com/mapbox/pbf
- Issues: https://github.com/mapbox/pbf/issues
- npm.io page: https://npm.io/package/pbf

## Dependencies (1)

- [resolve-protobuf-schema](https://npm.io/package/resolve-protobuf-schema.md) ^2.1.0

## Alternatives

- [flatbuffers](https://npm.io/package/flatbuffers.md) — 6.0M weekly downloads
- [jwt-simple](https://npm.io/package/jwt-simple.md) — 259.5K weekly downloads
- [@exodus/patch-broken-hermes-typed-arrays](https://npm.io/package/@exodus/patch-broken-hermes-typed-arrays.md) — 28.5K weekly downloads
- [@native-to-anchor/buffer-layout](https://npm.io/package/@native-to-anchor/buffer-layout.md) — 12.2K weekly downloads
- [binary-parser-encoder](https://npm.io/package/binary-parser-encoder.md) — 5.3K weekly downloads

## Recent versions

- 5.1.2 (latest) — 2026-07-09
- 5.1.1 — 2026-07-08
- 5.1.0 — 2026-05-29
- 5.0.0 — 2026-05-26
- 4.0.2 — 2026-05-22
- 4.0.1 — 2024-07-08
- 4.0.0 — 2024-07-08
- 3.3.0 — 2024-07-03
- 3.2.1 — 2019-10-11
- 3.2.0 — 2019-03-11
- 3.1.0 — 2017-09-27
- 3.0.5 — 2016-11-30
- 3.0.4 — 2016-11-14
- 3.0.3 — 2016-11-14
- 3.0.2 — 2016-09-30
- … 22 more at https://npm.io/package/pbf/versions

## README

# pbf

[![Node](https://github.com/mapbox/pbf/actions/workflows/node.yml/badge.svg)](https://github.com/mapbox/pbf/actions/workflows/node.yml)
![Bundle size](https://img.shields.io/bundlephobia/minzip/pbf)

A low-level, fast, ultra-lightweight (2.5KB gzipped) JavaScript library for decoding and encoding [protocol buffers](https://developers.google.com/protocol-buffers), a compact binary format for structured data serialization. Works both in Node and the browser. Supports lazy decoding and detailed customization of the reading/writing code.

## Performance

This library is fast — competitive with or faster than other JS protobuf implementations —
while being an order of magnitude smaller. Here's a result from a real-world benchmark on Node v26
(decoding and encoding 439 Mapbox vector tiles, 37.5 MB total; the equivalent JSON is 136 MB):

|| decode | encode | size (minzip) |
|---|---|---|---|
| **pbf** | 195ms, 192 MB/s | 146ms, 257 MB/s | 2.6 KB |
| [protocol-buffers](https://github.com/mafintosh/protocol-buffers) | 303ms, 124 MB/s | 612ms,  61 MB/s | 14.2 KB
| [protobuf.js](https://github.com/protobufjs/protobuf.js/) | 192ms, 195 MB/s | 141ms, 266 MB/s | 33.9 KB |
| JSON | 441ms, 308 MB/s | 363ms, 374 MB/s | — |

`JSON` throughput is measured against the 136 MB JSON payload, not the 37.5 MB pbf payload —
on the same data, pbf is ~2× faster to decode and ~2.5× faster to encode, and produces output
roughly a quarter the size. See `bench/bench-tiles.js`.

## Examples

#### Using Compiled Code

Install `pbf` and compile a JavaScript module from a `.proto` file:

```bash
$ npm install -g pbf
$ pbf example.proto > example.js
```

Then read and write objects using the module like this:

```js
import {PbfReader, PbfWriter} from 'pbf';
import {readExample, writeExample} from './example.js';

// read
const obj = readExample(new PbfReader(buffer));

// write
const pbf = new PbfWriter();
writeExample(obj, pbf);
const buffer = pbf.finish();
```

Alternatively, you can compile a protobuf schema file directly in the code:

```js
import {compile} from 'pbf/compile';
import schema from 'protocol-buffers-schema';

const proto = schema.parse(fs.readFileSync('example.proto'));
const {readExample, writeExample} = compile(proto);
```

#### Custom Reading

```js
const pbf = new PbfReader(buffer);
const data = readData(pbf);

function readData(pbf, end) {
    const data = {};
    let field;
    while ((field = pbf.nextField(end))) {
        if (field === 1) data.name = pbf.readString();
        else if (field === 2) data.version = pbf.readVarint();
        else if (field === 3) data.layer = readLayer(pbf, pbf.readVarint() + pbf.pos);
    }
    return data;
}
function readLayer(pbf, end) {
    const layer = {};
    let field;
    while ((field = pbf.nextField(end))) {
        if (field === 1) layer.name = pbf.readString();
        else if (field === 3) layer.size = pbf.readVarint();
    }
    return layer;
}
```

#### Custom Writing

```js
const pbf = new PbfWriter();
writeData(data, pbf);
const buffer = pbf.finish();

function writeData(data, pbf) {
    pbf.writeStringField(1, data.name);
    pbf.writeVarintField(2, data.version);
    pbf.writeMessage(3, writeLayer, data.layer);
}
function writeLayer(layer, pbf) {
    pbf.writeStringField(1, layer.name);
    pbf.writeVarintField(2, layer.size);
}
```

## Install

Install using NPM with `npm install pbf`, then import as a module:

```js
import {PbfReader, PbfWriter} from 'pbf';
```

Or use as a module directly in the browser with [jsDelivr](https://www.jsdelivr.com/esm):

```html
<script type="module">
    import {PbfReader, PbfWriter} from 'https://cdn.jsdelivr.net/npm/pbf/+esm';
</script>
```

Alternatively, there's a browser bundle exposing a `Pbf` global with `PbfReader` and `PbfWriter` properties:

```html
<script src="https://cdn.jsdelivr.net/npm/pbf"></script>
```

## API

The library exposes two classes: `PbfReader` for decoding and `PbfWriter` for encoding. Splitting them lets bundlers tree-shake the half you don't use.

Create a `PbfReader` from a `Buffer` or `Uint8Array`:

```js
// parse a pbf file from disk in Node
const pbf = new PbfReader(fs.readFileSync('data.pbf'));

// parse a pbf file in a browser after an ajax request with responseType="arraybuffer"
const pbf = new PbfReader(new Uint8Array(xhr.response));
```

Both classes expose the following properties:

```js
pbf.length; // length of the underlying buffer
pbf.pos; // current offset for reading or writing
```

#### Reading

Loop over a message's fields with `nextField` and dispatch on the field number. Unrecognized or unread fields are skipped automatically on the next iteration:

```js
let field;
while ((field = pbf.nextField(end))) {
    if (field === 1) obj.id = pbf.readVarint();
    else if (field === 2) obj.name = pbf.readString();
}
```

To read an embedded message, pass `pbf.readVarint() + pbf.pos` as `end` to a nested reader:

```js
const msg = readSubMessage(pbf, pbf.readVarint() + pbf.pos);
```

Read values:

```js
const value = pbf.readVarint();
const str = pbf.readString();
const numbers = pbf.readPackedVarint();
```

For lazy or partial decoding, save the position and come back to it later:

```js
let fooPos = -1;
let field;
while ((field = pbf.nextField())) {
    if (field === 1) fooPos = pbf.pos;
}
...
pbf.pos = fooPos;
const foo = readFoo(pbf, pbf.readVarint() + pbf.pos);
```

A callback-based `readFields(fn, obj, end)` is also available for backward compatibility, but new code should prefer the `nextField` loop — it's significantly faster.

Scalar reading methods:

* `readVarint(isSigned)` (pass `true` if you expect negative varints)
* `readSVarint()`
* `readFixed32()`
* `readFixed64()`
* `readSFixed32()`
* `readSFixed64()`
* `readBoolean()`
* `readFloat()`
* `readDouble()`
* `readString()`
* `readBytes()`

Field iteration methods:

* `nextField(end)` — returns the next field number, or `0` at end-of-message; skips the previous field's value if it wasn't consumed
* `skip(value)` — skips a field given its raw tag varint

Packed reading methods:

* `readPackedVarint(arr, isSigned)` (appends read items to `arr`)
* `readPackedSVarint(arr)`
* `readPackedFixed32(arr)`
* `readPackedFixed64(arr)`
* `readPackedSFixed32(arr)`
* `readPackedSFixed64(arr)`
* `readPackedBoolean(arr)`
* `readPackedFloat(arr)`
* `readPackedDouble(arr)`

#### Writing

Create a `PbfWriter` (optionally with a pre-allocated `Buffer` or `Uint8Array`):

```js
const pbf = new PbfWriter();
```

Write values:

```js
pbf.writeVarint(123);
pbf.writeString("Hello world");
```

Write an embedded message:

```js
pbf.writeMessage(1, writeObj, obj);

function writeObj(obj, pbf) {
    pbf.writeStringField(obj.name);
    pbf.writeVarintField(obj.version);
}
```

Field writing methods:

* `writeVarintField(tag, val)`
* `writeSVarintField(tag, val)`
* `writeFixed32Field(tag, val)`
* `writeFixed64Field(tag, val)`
* `writeSFixed32Field(tag, val)`
* `writeSFixed64Field(tag, val)`
* `writeBooleanField(tag, val)`
* `writeFloatField(tag, val)`
* `writeDoubleField(tag, val)`
* `writeStringField(tag, val)`
* `writeBytesField(tag, buffer)`

Packed field writing methods:

* `writePackedVarint(tag, val)`
* `writePackedSVarint(tag, val)`
* `writePackedSFixed32(tag, val)`
* `writePackedSFixed64(tag, val)`
* `writePackedBoolean(tag, val)`
* `writePackedFloat(tag, val)`
* `writePackedDouble(tag, val)`

Scalar writing methods:

* `writeVarint(val)`
* `writeSVarint(val)`
* `writeSFixed32(val)`
* `writeSFixed64(val)`
* `writeBoolean(val)`
* `writeFloat(val)`
* `writeDouble(val)`
* `writeString(val)`
* `writeBytes(buffer)`

Message writing methods:

* `writeMessage(tag, fn[, obj])`
* `writeRawMessage(fn[, obj])`

Misc methods:

* `realloc(minBytes)` - pad the underlying buffer size to accommodate the given number of bytes;
   note that the size increases exponentially, so it won't necessarily equal the size of data written
* `finish()` - make the current buffer ready for reading and return the data as a buffer slice

For an example of a real-world usage of the library, see [vector-tile-js](https://github.com/mapbox/vector-tile-js).


## Proto Schema to JavaScript

If installed globally, `pbf` provides a binary that compiles `proto` files into JavaScript modules. Usage:

```bash
$ pbf <proto_path> [--no-write] [--no-read] [--legacy]
```

The `--no-write` and `--no-read` switches remove corresponding code in the output.
The `--legacy` switch makes it generate a CommonJS module instead of ESM.

`Pbf` will generate `read<Identifier>` and `write<Identifier>` functions for every message in the schema. For nested messages, their names will be concatenated — e.g. `Message` inside `Test` will produce `readTestMessage` and `writeTestMessage` functions.


* `read(pbf)` - decodes an object from the given `PbfReader` instance.
* `write(obj, pbf)` - encodes an object into the given `PbfWriter` instance (usually empty).

The resulting code is clean and simple, so it's meant to be customized.

---
_Source: https://npm.io/package/pbf · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
