Skip to content

Every field in a schema needs a type, and this page is the reference for what's available. Scalar types map directly to primitives your language already has. Well-known types cover common structures like timestamps. Every type, scalar or well-known, gets a defined JSON representation too, covered in the ProtoJSON mapping below.

Numeric Types

int32 / int64
Signed integers with variable-length (varint) encoding. The default choice; when in doubt, int64 is the safe pick.
uint32 / uint64
Unsigned integers. Ideal for counts and sizes that can never be negative.
sint32 / sint64
Signed integers that use ZigZag encoding to keep small negative numbers compact. Use when values are frequently negative.
fixed32 / fixed64
Always 4/8 bytes. Beats a varint only for values consistently above 228 / 256, like hashes and large constants.
float / double
32-bit and 64-bit IEEE 754 floating point numbers.

Other Scalar Types

string
Always UTF-8 encoded text. Limited to 2GB.
bytes
Raw bytes, no encoding assumed.
bool
Encoded as a varint 0 or 1.
enum
Predefined set of named integers. Defaults to 0.

Every compiler ships a set of schemas already written for you, called well-known types, or WKTs for short. Google standardizes them, so you import google.protobuf.Timestamp or any other one without declaring a dependency yourself. Each WKT also gets its own JSON mapping, built for clean integration with web APIs. Full definitions are in the google.protobuf reference.

General

google.protobuf.Any
Holds an arbitrary serialized message and a type URL that identifies it. The payload can vary at runtime.
google.protobuf.Timestamp
A point in time, independent of timezone. Maps to RFC 3339 in JSON.
google.protobuf.Duration
A span of time. Maps to a string ending in 's' in JSON (e.g. '1.5s').
google.protobuf.Empty
Marks an API that takes no parameters, or returns nothing.
google.protobuf.FieldMask
A set of symbolic field paths. They mark which fields a read or update should touch.

Dynamic JSON

google.protobuf.Struct
Maps directly to a free-form JSON object.
google.protobuf.Value
Represents a dynamically typed value, equivalent to any JSON type.

Like every well-known type, these are ordinary messages, defined in plain .proto files you can read yourself. struct.proto defines Struct and Value. wrappers.proto defines the wrapper types, though you rarely need those now that optional gives you explicit presence.

One more family of WKTs describes APIs and types themselves. They power runtime reflection and API tooling, and you will rarely write them by hand. The most interesting are Method in api.proto, Type in type.proto, and SourceContext in source_context.proto.

JSON shows RFC 3339 and a seconds string; the bytes are just seconds and nanos fields encoded as varints.

schema.proto
edition = "2024";

package demo.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

message Job {
  google.protobuf.Timestamp scheduled_at = 1;
  google.protobuf.Duration timeout = 2;
}
Input JSON (editable)
{
  "scheduledAt": "2026-01-15T10:00:00Z",
  "timeout": "30s"
}
ProtoJSON

Compiling the schema...

Encode the same message twice and you get identical JSON both times. That is not an accident. Protobuf's wire format is binary, but it also defines a canonical ProtoJSON mapping, and every payload maps to exactly one JSON representation under it.

JSON mapping rules
Protobuf to JSON Type Mapping Rules
messageJSONObjectExample{"userName": "hiro"}Serialized as a JSON object. Field names are mapped to lowerCamelCase by default, or the json_name option if set.
repeatedJSONArrayExample["a", "b"]Serialized as a JSON array.
map<K, V>JSONObjectExample{"k": "v"}Serialized as a JSON object.
int32, uint32JSONNumberExample42Standard JSON numbers.
float, doubleJSONNumberExample123.45Standard JSON numbers.
boolJSONBooleanExampletrueStandard JSON booleans.
int64, uint64JSONStringExample"9007199254740993"Strings prevent precision loss in JS.
enumJSONStringExample"ROLE_ADMIN"Uses the string name of the enum value.
bytesJSONStringExample"NDI="Base64 encoded string.
google.protobuf.TimestampJSONStringExample"2023-10-01T12:00:00Z"RFC 3339 formatted timestamp string.
google.protobuf.DurationJSONStringExample"1.000340012s"Seconds with up to 9 fractional digits.
google.protobuf.FieldMaskJSONStringExample"f.a,f.b"Comma-separated paths as a single string.
google.protobuf.StructJSONObjectExample{"foo": "bar"}Standard representation for a generic JSON object.
google.protobuf.ValueJSONAnyExample"foo" or 123Can be any valid JSON value (null, number, string, boolean, struct, or list).
google.protobuf.NullValueJSONnullExamplenullThe JSON null value.
google.protobuf.EmptyJSONObjectExample{}An empty JSON object.

64-bit Precision

Try to hold 9007199254740993 in a JavaScript number and it becomes 9007199254740992. JavaScript numbers are 64-bit floats, and they lose precision for integers above 253 - 1.

That is why 64-bit integer types (int64, fixed64, uint64, sint64, and sfixed64) are encoded as strings in JSON, not numbers.

64-bit values are strings in JSON so JavaScript's floats can't corrupt them. The canonical output keeps the quotes.

schema.proto
edition = "2024";

package demo.v1;

message Record {
  int64 big_number = 1;
  bytes raw = 2;
  double ratio = 3;
}
Input JSON (editable)
{
  "bigNumber": "9007199254740993"
}
ProtoJSON

Compiling the schema...

Further Reading