Skip to content

Every field on the wire starts with a tag: the field number and a 3-bit wire type, packed into a single varint. The wire type tells the decoder how to read the bytes that follow.

Wire typeProtobuf types
0 (Varint)
int32int64uint32uint64sint32sint64boolenum
1 (I64)
fixed64sfixed64double
2 (LEN)
stringbytesmessagealso used for packed repeated scalars
3 (SGROUP)group start (deprecated)
4 (EGROUP)group end (deprecated)
5 (I32)
fixed32sfixed32float

Varint Fields (Wire Type 0)

Most numeric types use Wire Type 0. The length is implicit because the decoder reads bytes one by one until it finds a byte where the MSB is 0, using the same continuation-bit rule as the varint section earlier.

messageUser {int32age =1;}Schemaage:150Data+Encoded Payload08Tag96 01Value (Varint 150)00001000Field 1Type 010010110 00000001MSB=1 (Cont.)MSB=0 (End)

Fixed-Size Fields (Wire Type 1, 5)

Fixed-width types have a constant size, so they need no length prefix or continuation bits. Wire Type 1 is always 8 bytes, used by double and fixed64. Wire Type 5 is always 4 bytes, used by float and fixed32. The decoder reads that many bytes after the tag.

messageUser {floatheight =2;doubleweight =3;}Schemaheight:3.14weight:80.0Data+Encoded Payload15Tagc3 f5 48 40Value (3.14 float, Little-Endian)00010101Field 2Type 511000011 1111010101001000 01000000Fixed 32-bit (4 Bytes)19Tag00 00 00 00 00 00 54 40Value (80.0 double, Little-Endian)00011001Field 3Type 100000000 00000000 00000000 0000000000000000 00000000 01010100 01000000Fixed 64-bit (8 Bytes)

Length-Delimited (Wire Type 2)

string, bytes, and nested message fields use Length-Delimited encoding. These fields carry an explicit length prefix, encoded as a varint, right after the tag. That prefix tells the decoder exactly how many of the bytes that follow belong to this field.

messageUser {stringname =3;}Schemaname:"Alice"Data+Encoded Payload1aTag05Len41 6c 69 63 65"Alice"00011010Field 3Type 200000101Length 501000001 0110110001101001 01100011 01100101"Alice" ASCII

Packed Repeated Fields

Repeated scalar fields are packed by default. On the wire that means one tag and one length prefix for the whole list instead of a tag per element. Set features.repeated_field_encoding = EXPANDED on a file or a field to get the older one-tag-per-element layout back.

EXPANDED
Schema
repeated int32 ids = 1 [features.repeated_field_encoding = EXPANDED];
Values
[1, 2, 3]
Wire Layout
08
Tag
01
Val
08
Tag
02
Val
08
Tag
03
Val

Each element repeats the field tag, which adds up for many small elements.

PACKED (EDITION 2024 DEFAULT)
Schema
repeated int32 ids = 1;
Values
[1, 2, 3]
Wire Layout
0a
Tag
03
Len
01
Data
02
Data
03
Data

Elements are concatenated into a single length-delimited record with one tag.

Further Reading

Next

Map Encoding

How Protobuf maps look on the wire: each entry as a repeated key/value message, and why that representation keeps old decoders compatible.