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.

Language mappings follow the generated code of each official runtime, with TypeScript from Protobuf-ES and Python from protobuf-py.

Non-Numeric Types

string
Always UTF-8 encoded text. Limited to 2GB.
LanguageTypeLiteral
Gostring"hi"
TypeScriptstring"hi"
JavaString"hi"
Pythonstr"hi"
C++std::string"hi"
ProtoJSONstring"hi"
bytes
Raw bytes, no encoding assumed.
LanguageTypeLiteral
Go[]byte[]byte("hi")
TypeScriptUint8Arraynew Uint8Array([104, 105])
JavaByteStringByteString.copyFromUtf8("hi")
Pythonbytesb"hi"
C++std::stringstd::string("hi")
ProtoJSONbase64 string"aGk="
bool
Encoded as a varint 0 or 1.
LanguageTypeLiteral
Gobooltrue
TypeScriptbooleantrue
Javabooleantrue
PythonboolTrue
C++booltrue
ProtoJSONtrue / falsetrue
enum
Predefined set of named integers. Defaults to 0.
LanguageTypeLiteral
Gonamed int32pb.Status_ACTIVE
TypeScriptenumStatus.ACTIVE
JavaenumStatus.ACTIVE
PythonIntEnumStatus.ACTIVE
C++enumACTIVE
ProtoJSONstring (the value name)"ACTIVE"

Numeric Types

int32
Signed 32-bit integer with variable-length (varint) encoding. Negative values always take 10 bytes, so prefer sint32 when they are common.
LanguageTypeLiteral
Goint3242
TypeScriptnumber42
Javaint42
Pythonint42
C++int32_t42
ProtoJSONnumber42
int64
Signed 64-bit integer with varint encoding. The default choice; when in doubt, int64 is the safe pick.
LanguageTypeLiteral
Goint6442
TypeScriptbigint42n
Javalong42L
Pythonint42
C++int64_t42
ProtoJSONstring"42"
uint32
Unsigned 32-bit integer with varint encoding. Ideal for counts and sizes that can never be negative.
LanguageTypeLiteral
Gouint3242
TypeScriptnumber42
Javaint42
Pythonint42
C++uint32_t42
ProtoJSONnumber42
uint64
Unsigned 64-bit integer with varint encoding. Ideal for large counts, sizes, and identifiers.
LanguageTypeLiteral
Gouint6442
TypeScriptbigint42n
Javalong42L
Pythonint42
C++uint64_t42
ProtoJSONstring"42"
sint32
Signed 32-bit integer that uses ZigZag encoding to keep small negative numbers compact.
LanguageTypeLiteral
Goint3242
TypeScriptnumber42
Javaint42
Pythonint42
C++int32_t42
ProtoJSONnumber42
sint64
Signed 64-bit integer with ZigZag encoding. Use when values are frequently negative.
LanguageTypeLiteral
Goint6442
TypeScriptbigint42n
Javalong42L
Pythonint42
C++int64_t42
ProtoJSONstring"42"
fixed32
Always 4 bytes, unsigned. Beats a varint only for values consistently above 228, like hashes.
LanguageTypeLiteral
Gouint3242
TypeScriptnumber42
Javaint42
Pythonint42
C++uint32_t42
ProtoJSONnumber42
fixed64
Always 8 bytes, unsigned. Beats a varint only for values consistently above 256, like hashes and large constants.
LanguageTypeLiteral
Gouint6442
TypeScriptbigint42n
Javalong42L
Pythonint42
C++uint64_t42
ProtoJSONstring"42"
sfixed32
Always 4 bytes, signed. The fixed-width counterpart of int32.
LanguageTypeLiteral
Goint3242
TypeScriptnumber42
Javaint42
Pythonint42
C++int32_t42
ProtoJSONnumber42
sfixed64
Always 8 bytes, signed. The fixed-width counterpart of int64.
LanguageTypeLiteral
Goint6442
TypeScriptbigint42n
Javalong42L
Pythonint42
C++int64_t42
ProtoJSONstring"42"
float
32-bit IEEE 754 floating point number.
LanguageTypeLiteral
Gofloat323.14
TypeScriptnumber3.14
Javafloat3.14f
Pythonfloat3.14
C++float3.14f
ProtoJSONnumber, or "NaN" / "Infinity"3.14
double
64-bit IEEE 754 floating point number.
LanguageTypeLiteral
Gofloat643.14
TypeScriptnumber3.14
Javadouble3.14
Pythonfloat3.14
C++double3.14
ProtoJSONnumber, or "NaN" / "Infinity"3.14

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.
LanguageTypeLiteral
Go*anypb.Anyanypb.New(msg)
TypeScriptAnyanyPack(UserSchema, user)
Javacom.google.protobuf.AnyAny.pack(msg)
Pythonprotobuf.wkt.AnyAny.pack(msg)
C++google::protobuf::Anyany.PackFrom(msg)
ProtoJSONobject with "@type"{"@type": "...", ...}
google.protobuf.Timestamp
A point in time, independent of timezone. Maps to RFC 3339 in JSON.
LanguageTypeLiteral
Go*timestamppb.Timestamptimestamppb.Now()
TypeScriptTimestamptimestampNow()
Javacom.google.protobuf.TimestampTimestamps.fromMillis(ms)
Pythonprotobuf.wkt.TimestampTimestamp.now()
C++google::protobuf::TimestampTimeUtil::GetCurrentTime()
ProtoJSONRFC 3339 string"2026-01-15T10:00:00Z"
google.protobuf.Duration
A span of time. Maps to a string ending in 's' in JSON (e.g. '1.5s').
LanguageTypeLiteral
Go*durationpb.Durationdurationpb.New(30 * time.Second)
TypeScriptDurationcreate(DurationSchema, { seconds: 30n })
Javacom.google.protobuf.DurationDurations.fromSeconds(30)
Pythonprotobuf.wkt.DurationDuration.from_seconds(30)
C++google::protobuf::DurationTimeUtil::SecondsToDuration(30)
ProtoJSONstring ending in "s""30s"
google.protobuf.Empty
Marks an API that takes no parameters, or returns nothing.
LanguageTypeLiteral
Go*emptypb.Empty&emptypb.Empty{}
TypeScriptEmptycreate(EmptySchema)
Javacom.google.protobuf.EmptyEmpty.getDefaultInstance()
Pythonprotobuf.wkt.EmptyEmpty()
C++google::protobuf::EmptyEmpty()
ProtoJSON{}{}
google.protobuf.FieldMask
A set of symbolic field paths. They mark which fields a read or update should touch.
LanguageTypeLiteral
Go*fieldmaskpb.FieldMaskfieldmaskpb.New(msg, "name")
TypeScriptFieldMaskcreate(FieldMaskSchema, { paths: ["name"] })
Javacom.google.protobuf.FieldMaskFieldMaskUtil.fromString("name")
Pythonprotobuf.wkt.FieldMaskFieldMask(paths=["name"])
C++google::protobuf::FieldMaskFieldMaskUtil::FromString("name")
ProtoJSONcomma-separated string"name"

Dynamic JSON

google.protobuf.Struct
Maps directly to a free-form JSON object.
LanguageTypeLiteral
Go*structpb.Structstructpb.NewStruct(m)
TypeScriptStructfromJson(StructSchema, { k: "v" })
Javacom.google.protobuf.StructStruct.newBuilder().putFields(…)
Pythonprotobuf.wkt.StructStruct.from_python({"k": "v"})
C++google::protobuf::Struct(*s.mutable_fields())["k"]
ProtoJSONobject{"k": "v"}
google.protobuf.Value
Represents a dynamically typed value, equivalent to any JSON type.
LanguageTypeLiteral
Go*structpb.Valuestructpb.NewStringValue("x")
TypeScriptValuefromJson(ValueSchema, "x")
Javacom.google.protobuf.ValueValues.of("x")
Pythonprotobuf.wkt.ValueValue.from_python("x")
C++google::protobuf::Valuev.set_string_value("x")
ProtoJSONany JSON value"x"

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.

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