Type
Every field has a type: string, int32, bool, a message, an enum. In statically typed languages, code that tries to put text in an int32 field doesn't compile, and in Python the generated class raises an error as soon as you try. A JSON API can get typed models too, either hand-written or generated from an OpenAPI spec. A Protobuf schema also defines the wire format and the rules for changing it safely. Decoding is more forgiving, because the binary format doesn't carry enough type information to catch every mismatch. A string sent where the schema expects an int32 parses without an error and lands in the message's unknown fields. Each language maps every Protobuf type onto one of its own, and the Types reference lists them all.
Name
The name is used by generated code and by people reading the schema. It becomes the accessor in Go or TypeScript and the key in the JSON mapping, and it is never written into the binary encoding. That is why you can rename a field without breaking binary readers. It is also why renaming still breaks JSON consumers and every line of code that used the old accessor, so treat a rename as a breaking change.
Number
Instead of sending a name like "username" over the wire, Protobuf identifies each field by its number. On the wire it is packed together with the field's wire type into a single value called the tag, and the Binary chapter shows exactly how. Because the number is the field's identity, it must never change once a message type is in use, and a number must never be reused for a different field: old data would decode as the new field without any error.
Tip: Numbers 1 through 15 take 1 byte to encode (including the field number and wire type). Use 1-15 for your most frequently sent fields.
Numbers go up to 536,870,911, except 19000 through 19999, which Protobuf reserves for its own use.
message User {
string username = 1;
}
{
"username": "hiro"
}Each case pairs a different schema with a message that fits it. Switch between them to see how the type and the number shape what lands on the wire.
Numbers 1 through 15 pack into a single tag byte; field 16 needs two. Open the Bytes tab and compare the two tags.
edition = "2024"; package demo.v1; message Metrics { int32 low_number = 1; int32 high_number = 16; }
Compiling the schema...
Further Reading
- Tip of the week #1: Field names are forever
Why renaming a field is safe on the wire but still a breaking change almost everywhere else.
- Tip of the week #9: Some numbers are more equal than others
How field numbers pack into tags, and why numbers 1 through 15 should go to your most common fields.
Next
Enums
Defining enums in Protobuf: the required zero value, open enums, naming conventions, and how unknown enum values behave across versions.