Developer library · Dart, TypeScript, Java
firestore-proto-codec
Store protos in Firestore without inventing your own mapping
// Write protos to documents
await doc.set(codec.encode(foo));
// Or individual fields
await doc.set({'foo': codec.encode(foo)});
// And read them back
final foo = codec.decode(snapshot.data()!, Foo());Numbers stay numbers, timestamps stay timestamps, and blobs stay blobs.
In progress2026protobuf, firestore, dart, typescript, java
Protocol Buffers are a good way to define the shape of your data. Firestore is a good place to put that data. The join between them is where it gets awkward: there is no canonical way to write a protobuf message into a Firestore document, so every project invents one, and the inventions disagree.
firestore-proto-codec is that mapping, written down and pinned by tests. A
pure function in each direction:
encode(Message) -> Map<String, FirestoreValue>
decode(Map<String, FirestoreValue>, MessageType) -> Message
A message encodes to a map of Firestore-native values — the thing you hand to
set(). Nested messages encode by the same rule, so writing a whole document
and writing a single field are the same operation at different depths.
Why not proto3 JSON
Proto3 JSON is the obvious mapping, and it is wrong for Firestore in ways that fail silently — the write succeeds and the query is quietly incorrect.
| proto3 JSON | this codec | |
|---|---|---|
int64 |
String, so it sorts lexicographically and breaks every range query | Integer |
bytes |
base64 String | Blob |
Timestamp |
RFC-3339 String | Timestamp, orderable and indexable |
double NaN/±Inf |
String | Double |
| Field names | lowerCamelCase, and each runtime’s default differs |
the proto name, verbatim |
Several of those choices are not conventions at all. Firestore’s own wire format
is itself a protobuf: google.firestore.v1.Value declares bytes bytes_value,
google.protobuf.Timestamp timestamp_value, and
google.type.LatLng geo_point_value. For those types the mapping is the
identity — the codec is recovering a correspondence that was already there.
Scope
Collection paths, document ids, security rules, index definitions, field masks, merge semantics, and schema migration are all deliberately out of scope. They are properties of an application’s schema rather than of the encoding. A codec that knows about collections is a persistence framework, and this is not one.
Where it stands
Draft, v0.1. The encoding lives in a written specification and is pinned by 24 shared conformance vectors; the Dart, TypeScript, and Java implementations all pass them, and a conforming implementation in any other language has to pass the same set. Not yet published, pending a decision on the extension number used by the per-field encoding options.