Protobuf Decoder
Paste a protobuf payload and read it — no schema needed. Every plausible interpretation of each field is shown side by side, because the wire format genuinely is ambiguous without the .proto.
Avro's resolution rules are specified, so this gives a definite answer: backward means a reader on the new schema can read old data, forward means a reader on the old schema can read new data, and full means both. Protobuf has three different notions of "compatible" and a change can be safe under one while breaking another — renaming a field is invisible on the wire and fatal for protobuf JSON. All three are reported separately rather than blended into one verdict.
{{ error }}
They differ only in documentation, aliases, defaults or formatting — the canonical
forms are identical, so nothing about reading data changes.These are the same schema
{{ v.label }}
{{ v.ok ? v.okText : v.badText }}
{{ grp.label }}{{ grp.issues.length }}
- {{ d.path }} — {{ d.message }}
No differences that affect compatibility.
A protobuf payload is decodable without its schema — every field carries its
number and wire type. What it does not carry is names or declared types, so the same bytes
genuinely have several valid readings. Every plausible one is shown rather than one
confident guess. Paste a .proto below to resolve the names.
Add a .proto to resolve field names (optional)
{{ error }}
{{ framing.note }}
- {{ w }}
Field number{{ schemaNotes.unknown.length === 1 ? '' : 's' }} {{ schemaNotes.unknown.join(', ') }} appear in the payload but not in the schema — that is what version skew looks like on the wire.
Not present in this payload: {{ schemaNotes.missing.join(', ') }}. In proto3 an unset field and a zero value are indistinguishable, so absence here does not prove it was never set.
-
{{ f.number }}
{{ f.name }}
{{ f.wireName }}
declared {{ f.declaredType }}
- {{ r.as }}{{ r.value }}
Nothing decoded from those bytes.
Deterministic on purpose — the same schema always produces the same sample, so it can go straight into a fixture or a test without changing under you. Output follows protobuf's canonical JSON mapping, which is where the surprises live: lowerCamelCase keys, 64-bit integers as strings, enums as symbol names, and Timestamp as a string rather than an object.
{{ sampleText || 'Generated JSON appears here…' }}
{{ error }}
Everything is parsed and analysed entirely in your browser. Nothing you paste is ever uploaded to a server.
How much a protobuf payload tells you on its own
More than people expect, and less than you need. Every field is preceded by a tag holding its field number and its wire type, so the structure of a message is always recoverable from the bytes alone. What the bytes never carry is the field's name or its declared type.
Why one field shows several values
Because they are all correct. A varint holding 150 is a valid int64 of 150, a valid
sint32 of 75 under zigzag encoding, and not a valid bool at all. Bytes
in a length-delimited field may be a UTF-8 string or a nested message — often both parse. This
ambiguity is a property of the format, so every reading is listed rather than one being guessed
at. Paste your .proto and the matching reading is highlighted.
The zero byte that stops payloads parsing
A payload copied out of a Kafka topic frequently begins with a zero byte followed by four more, and then refuses to decode. That is Confluent Schema Registry framing — a magic byte plus a big-endian schema id — and those five bytes are not protobuf. This tool spots it and strips it. The schema id itself cannot be resolved without contacting a registry, which this tool does not do, so it is reported rather than followed.
How it decides something is not protobuf at all
Four things are decisive rather than merely odd: a length prefix claiming more bytes than remain, an undefined wire type, field number zero, and a varint longer than ten bytes. Any of them means the input cannot be a valid message, so decoding stops with a reason instead of emitting plausible-looking nonsense. Nested-message guessing is also depth-capped, because a hostile payload should not be able to hang the page.
Have the schema and want to read it properly?
Use the Protobuf Schema Viewer, which shows field presence, edition features and JSON names.
Frequently asked questions
Can I really decode protobuf without the .proto file?
Partly, and it is worth understanding exactly how far. Every field on the wire carries a tag holding its number and its wire type, so the structure is always recoverable. What is not on the wire is the field's name or its declared type — so a varint could be an int32, a bool or a zigzag sint32, and length-delimited bytes could be a string or a nested message. All the valid readings are shown; choosing between them needs the schema.
Why does one field show several different values?
Because they are all correct readings of the same bytes. This is inherent to the format, not a limitation of the tool. Paste your .proto and the reading matching the declared type is highlighted, which is the only way to resolve it honestly.
What does the zero byte at the start of my payload mean?
That is Confluent Schema Registry framing: a zero magic byte followed by a four-byte big-endian schema id, then the actual payload. It is why a payload copied from a Kafka topic often refuses to parse — the first five bytes are not protobuf. The tool detects it and strips them. The schema id itself cannot be resolved without contacting a registry, which this tool does not do.
It says my payload is not protobuf. How does it know?
A few things are decisive rather than merely suspicious: a length prefix claiming more bytes than remain, an undefined wire type, field number zero, or a varint longer than ten bytes. Any of those means the bytes cannot be a valid protobuf message, so the decode stops and says so instead of producing plausible-looking nonsense.
Why are nested messages only shown as a possibility?
Because many byte sequences parse as some valid message purely by accident — the format is permissive enough that a random string often decodes as fields. Nested structure is therefore offered as one reading among several, never asserted, unless your .proto says the field is a message.
Is my payload uploaded anywhere?
No. Decoding happens entirely in your browser, which matters a great deal here — payloads captured from production usually contain real customer data.