Skip to main content
Proto3 provides a rich set of scalar types that map to built-in types in each target language. Choosing the right type affects both correctness (e.g., signed vs. unsigned) and encoding efficiency (e.g., variable-length vs. fixed-width).

Scalar types overview

Proto typeWire typeDefaultDescription
double64-bit (1)064-bit IEEE 754 double precision
float32-bit (5)032-bit IEEE 754 single precision
int32Varint (0)0Signed 32-bit integer; variable-length; inefficient for negatives
int64Varint (0)0Signed 64-bit integer; variable-length; inefficient for negatives
uint32Varint (0)0Unsigned 32-bit integer; variable-length
uint64Varint (0)0Unsigned 64-bit integer; variable-length
sint32Varint (0)0Signed 32-bit; ZigZag encoding; efficient for negatives
sint64Varint (0)0Signed 64-bit; ZigZag encoding; efficient for negatives
fixed3232-bit (5)0Always 4 bytes; unsigned; efficient when values are often > 2²⁸
fixed6464-bit (1)0Always 8 bytes; unsigned; efficient when values are often > 2⁵⁶
sfixed3232-bit (5)0Always 4 bytes; signed
sfixed6464-bit (1)0Always 8 bytes; signed
boolVarint (0)falseBoolean; encoded as varint 0 or 1
stringLength-delimited (2)""UTF-8 or 7-bit ASCII text
bytesLength-delimited (2)b""Arbitrary binary data

Integer types in detail

int32 and int64

General-purpose signed integers using variable-length varint encoding. Negative values always occupy 10 bytes in the binary format because the sign bit is preserved in the full 64-bit representation.
message Counter {
  int32 count = 1;       // efficient for non-negative values
  int64 large_count = 2; // efficient for non-negative values
}

uint32 and uint64

Unsigned integers using varint encoding. Cannot represent negative values — the compiler will reject negative literals.
message FileInfo {
  uint32 permissions = 1; // e.g. Unix file permissions
  uint64 size_bytes = 2;
}

sint32 and sint64

Signed integers that use ZigZag encoding. When a field regularly holds negative values, use sint32 / sint64 instead of int32 / int64 — they encode -1 as 1, -2 as 3, etc., keeping the encoded size small.
message TemperatureReading {
  sint32 celsius = 1; // negative values encoded efficiently
  sint64 microdelta = 2;
}

fixed32 and fixed64

Always encode as exactly 4 or 8 bytes, regardless of value. More efficient than varint for large numbers that are consistently above 2²⁸ (for fixed32) or 2⁵⁶ (for fixed64).
message HashDigest {
  fixed64 hash_hi = 1;
  fixed64 hash_lo = 2;
}

sfixed32 and sfixed64

Like fixed32 / fixed64 but interpret the stored bits as a signed two’s complement integer.
message Coordinate {
  sfixed32 x = 1;
  sfixed32 y = 2;
}

Floating point types

float

IEEE 754 single-precision (32-bit). Always encoded as exactly 4 bytes (wire type 5).

double

IEEE 754 double-precision (64-bit). Always encoded as exactly 8 bytes (wire type 1).
message Point3D {
  double x = 1;
  double y = 2;
  double z = 3;
}
Avoid comparing floats and doubles with == in application code — floating-point arithmetic is not exact. In JSON encoding, NaN, Infinity, and -Infinity are represented as the string literals "NaN", "Infinity", and "-Infinity".

Boolean type

bool is encoded as a varint 0 (false) or 1 (true). In proto3 the default value is false.
message FeatureFlags {
  bool enable_dark_mode = 1;
  bool enable_notifications = 2;
}

String type

string fields must contain valid UTF-8 encoded text or 7-bit ASCII. The content is encoded as a length-delimited byte sequence. The default value is the empty string "".
message Person {
  string name = 1;
  string email = 2;
}
The protobuf runtime validates UTF-8 encoding when reading and writing string fields in most languages. If you need to store arbitrary binary data, use bytes.

Bytes type

bytes fields hold arbitrary binary data with no encoding requirements. Like string, they use wire type 2. The default value is an empty byte sequence.
message Image {
  string mime_type = 1;
  bytes data = 2;
}

Language type mappings

Proto typeC++ type
doubledouble
floatfloat
int32int32_t
int64int64_t
uint32uint32_t
uint64uint64_t
sint32int32_t
sint64int64_t
fixed32uint32_t
fixed64uint64_t
sfixed32int32_t
sfixed64int64_t
boolbool
stringstd::string
bytesstd::string

Choosing the right integer type

Use int32 or int64 for general-purpose signed integers where values are expected to be non-negative or only occasionally negative. Examples: item counts, IDs, page numbers.
Use sint32 or sint64 when the field frequently holds negative values, such as temperature readings, financial deltas, or offsets. ZigZag encoding keeps these compact.
Use uint32 or uint64 for values that are always non-negative and whose maximum value is important — for example, Unix timestamps in seconds (uint32 until year 2106), port numbers, or bitfields.
Use fixed32 or fixed64 when values are uniformly large (greater than 2²⁸ for fixed32, 2⁵⁶ for fixed64) and the constant encoding size is preferable — for example, hash values, UUIDs, or cryptographic keys stored as integers.

Build docs developers (and LLMs) love