DEVELOPER & DATA

Protobuf Wire Decoder

Decode a protobuf payload from hex or base64 without the .proto: field numbers, wire types, nested messages, and every reading the same bytes could have.

A field tree with the number, wire type and value of each field, nested messages expanded, the alternative readings listed where the bytes are ambiguous, and a JSON version to copy.

Example: 22 bytes decode to four fields: a varint 150, the string "testing", a nested message holding another 150, and a 32-bit float 1 — with what each could also be.

v0.1.0 · last reviewed 22 September 2026
Loading the workspace…
BUILT TO BE UNDERSTOOD

Three bits of type,
and no names at all.

What the wire format actually carries, why several readings are equally valid, and what a decoder can never recover.

The wire format

A protobuf message is a flat sequence of fields, each one a tag followed by its payload. The tag is a varint holding the field number shifted left three bits with the wire type in the bottom three: 0 for a varint, 1 for eight bytes, 2 for a length-prefixed blob, 5 for four bytes. That is the entire type system on the wire. There is no magic number and no checksum, so the strongest evidence that a payload really is protobuf is that every byte of it is consumed by well-formed fields — which is exactly the test this page applies, and the same test it uses to decide whether a blob is a nested message.

Why the readings are plural

Without the .proto that produced it, several readings of the same bytes are equally valid. A varint of 1 is a uint64, an int64, a zigzag sint, a bool, or an enum member — the bytes do not say. Four bytes are a float or two kinds of 32-bit integer. A length-delimited blob is a string, a byte array, a nested message or a packed repeated field, and quite often it parses convincingly as more than one. So the page shows the reading it picked, marks it as a guess where the bytes are genuinely ambiguous, and lists the alternatives beside it rather than choosing silently. Field names are simply gone: field 1 is field 1, and only the schema knows it was called user_id.

Limits

Complete messages only. A gRPC or length-prefixed stream frame carries extra header bytes that have to come off first, groups (wire types 3 and 4, removed from proto3) are refused rather than guessed at, and a varint longer than ten bytes is reported as malformed with its offset. No .proto is fetched, parsed or applied, and nothing is sent anywhere — paste a payload from a log or a capture and it is decoded in your browser. Negative int32 and int64 values are always ten bytes on the wire, which is the reason the zigzag sint types exist. Nothing leaves the browser; the same four anonymous usage counts as the rest of the site apply.

SOURCES

  • Wire format only: tag = field number << 3 | wire type, then a varint, eight bytes, a length-prefixed blob or four bytes; field names live in the .proto and cannot be recovered from the bytes

Last reviewed 22 September 2026. How results are checked: How we verify.