Protobuf Schema Preview

Paste a .proto file to parse and visualize messages, services, enums, and fields. 100% client-side.

Last reviewed: April 2026

New to this tool? Click here for instructions

Input .proto
Parsed Structure
Paste a .proto file above to parse it.

What This Tool Does

This tool parses a Protocol Buffer schema file (.proto) entirely in your browser and renders its full structure as a color-coded tree: every message, service, enum, and RPC is extracted, along with the label (optional, required, repeated, or map), type, field number, and any nested definitions. A summary row reports the schema's syntax declaration, package name, and totals so you can sanity-check a file before sending it to protoc or committing it to a shared API repo.

Three view modes cover the common use cases. Parse mode shows the visual tree with badges, field tables, and RPC arrows. JSON View emits the parsed schema as a plain JSON object that you can pipe into downstream tooling, diff against a previous version, or pretty-print for a code review comment. Reference mode opens the protobuf scalar type table - useful when you need a quick reminder of which wire type backs sint32 versus fixed32 or what the default value for a bytes field is. Every computation runs client-side; no schema you paste is ever uploaded.

How to Use It

Three steps cover the typical workflow: paste, scan, switch.

Step 1 - Paste Your .proto File

Drop the contents of any .proto file into the input pane on the left. The parser handles the four kinds of top-level declaration: syntax, package, import, and any combination of message, service, and enum. Single-line (//) and block (/* … */) comments are stripped before parsing so they will not confuse the tokenizer. Click Try Example to pre-load a representative OrderService schema with nested messages, a service definition, an enum, and a streaming RPC.

Step 2 - Scan the Field Table

Each parsed message becomes a card. Fields are listed in field-number order with the field number printed in dim grey on the left, the label in amber, the type in green, and the field name in the default text colour. Nested messages cascade beneath their parent with an arrow indicator. The summary chips above the cards report the totals: 5 messages, 1 service, 1 enum, 26 fields. Mismatches against your mental model surface immediately - a missing repeated label or a duplicated field number jumps out.

Step 3 - Switch to JSON or Reference

The JSON View chip swaps the visual tree for the same data as a JSON object. Use this when you want to copy the parsed schema into a script, store it in a database, or compare two parses with a diff tool. The Reference chip opens the scalar type reference - the canonical mapping from int32, sint32, fixed32, uint32, etc., to their wire types and default values. Copy and Download buttons emit the JSON form, which is the most portable representation across downstream tools.

Worked Example: OrderService with Nested Messages

The bundled example is a realistic e-commerce OrderService schema. It exercises every part of the parser: syntax = "proto3", a package declaration, an import, a top-level enum with a zero-valued unspecified sentinel, nested Address and LineItem messages used as field types in Order, a map<string,string> field, a well-known google.protobuf.Timestamp field, and a service with three unary RPCs plus one server-streaming RPC.

  1. Click Try Example. The input pane fills with the schema. Parsing kicks off automatically after a 200 ms debounce.
  2. Read the summary chips. Expect syntax: proto3, pkg: ecommerce.v1, 9 messages, 1 service, 1 enum, 26 fields.
  3. Open the Order message card. Field 1 is string order_id, field 3 is repeated LineItem items (the repeated label is highlighted in amber), field 9 is map<string,string> metadata (note the map label and composite type).
  4. Open the OrderService card. Three RPCs print as rpc GetOrder (GetOrderRequest) → (GetOrderResponse). The fourth, WatchOrders, prints (ListOrdersRequest) → (stream GetOrderResponse) - the stream keyword on the response side flags it as server-streaming.
  5. Switch to JSON View. Copy the JSON output; it is a faithful AST of the schema and a useful artefact for code review or schema-diff tooling.

The diagram below shows how a single Order message encodes onto the wire. Each field becomes a tag-value pair where the tag packs the field number and a 3-bit wire-type code into a single varint. The figure traces the byte layout for field 1 (order_id = "A42"), field 5 (status = ORDER_STATUS_PAID, an enum encoded as varint 2), and field 6 (total_amount = 49.99, encoded as a fixed 64-bit IEEE-754 double).

Protobuf wire format byte layout for an Order message Diagram showing how three fields of an Order message encode onto the wire as tag-length-value or tag-value triples, with the tag varint decomposed into field number and wire type. Protobuf Wire Format - Order Message Encoding tag = (field_number << 3) | wire_type field 1 string order_id = "A42"; 0x0A 00001010 tag 0x03 len=3 length 'A' 0x41 '4' 0x34 '2' 0x32 UTF-8 bytes ("A42") 00001 010 field=1 type=2 field 5 OrderStatus status = ORDER_STATUS_PAID; 0x28 00101000 tag 0x02 varint=2 value (PAID) 00101 000 → field=5 type=0 (varint) field 6 double total_amount = 49.99; 0x31 00110001 tag 8 bytes: little-endian IEEE-754 0xC3 F5 28 5C 8F C2 48 40 double = 49.99 Wire Types Tag byte (varint) Length prefix (type 2 only) Field value bytes 3-bit Wire Type Codes 0 = varint (int, bool, enum) 1 = 64-bit fixed (double, fixed64) 2 = length-delimited (string, bytes, embedded msg, packed repeated) 5 = 32-bit fixed (float, fixed32) Key Property Field numbers 1-15 fit in one byte (4 bits number + 3 bits wire type + 1 continuation bit = 8 bits). Reserve those numbers for hot-path fields. Varint Encoding 7 data bits per byte; high bit set means continue. Small numbers cost one byte; very large numbers cost up to 10 bytes for a 64-bit value.
Three fields of an Order message on the wire. Each field is a tag byte followed by either a length-prefix + bytes (string), a varint (enum), or 8 fixed bytes (double). The tag varint decomposes into a field number and a 3-bit wire-type code, which together let the decoder route each value into the right field even if the message schema has evolved.

Common Use Cases

Protobuf shows up wherever schema and bytes-on-the-wire matter more than human readability. Five contexts dominate.

gRPC Service Contracts

Every gRPC service is defined in a .proto file. The service block lists RPCs, each with a request and response message type and an optional stream qualifier on either side. protoc with the gRPC plugin generates client stubs and server skeletons in 11+ languages from the same file, ensuring that a Go server and a Python client speak the identical wire protocol without anyone hand-writing serialization code. Streaming RPCs - server-streaming, client-streaming, and bidirectional - are first-class citizens of the schema, so a long-lived watch endpoint or a high-volume telemetry uplink is one keyword change rather than a protocol redesign.

Schema-First API Design

Teams that adopt a schema-first workflow start every API change in the .proto file. The schema is the source of truth: code is generated from it, documentation is generated from it, mock servers are generated from it. This inverts the common REST pattern (write the controller, document the controller, hope the docs stay in sync) and eliminates entire categories of drift bugs. Buf's buf breaking command can lint a PR's schema change against the previous commit and refuse merges that introduce wire-incompatible diffs, making the schema contract enforceable in CI rather than relying on reviewer vigilance.

Mobile-Server Contract Enforcement

Mobile clients ship to app stores on a delay and run on devices the server cannot force-update. Protobuf's strict forward and backward compatibility rules - never change a field number, never reuse a deleted number, only add new fields with new numbers - protect a fleet of old clients from server-side schema changes. An old client simply skips unknown fields rather than crashing. This is materially harder to achieve with JSON-over-REST, where renamed keys silently disappear and missing-required-field validation is wired by hand at each endpoint.

Log Message Normalisation

High-volume log pipelines (Kafka, BigQuery, Pulsar) increasingly use protobuf as the log line format instead of JSON. The 3-10x size reduction directly cuts storage and inter-region bandwidth costs, and a shared .proto registry gives consumers an enforceable schema rather than the implicit hope that producers will keep emitting the same JSON keys. The JSON Formatter tool is helpful when bridging between a protobuf-encoded log stream and a JSON-oriented downstream consumer.

Evolving APIs Without Breaking Changes

The combination of integer field numbers, the reserved keyword, and the unknown-field-skipping rule lets a service evolve its schema for years without breaking older clients. Add a new field as repeated string tags = 12; today; clients that haven't been recompiled will simply ignore the new field, while new clients can read it from servers that haven't been updated to populate it. The combination amounts to a wire-level type system that survives independent deploy cycles for client and server - a property that costs significant ceremony to retrofit onto a JSON API.

Cross-Language Code Generation

One .proto file feeds protoc with a target-language plugin and out come idiomatic data classes in Go, Java, Python, C++, C#, Ruby, Rust, Swift, Kotlin, Dart, PHP, Objective-C, JavaScript, or TypeScript. Each generator produces type-safe accessors, builders, equality, hashing, and serialization at the language's idiomatic surface (Pythonic @property setters, Rust's #[derive(Clone, Debug, PartialEq)], Go's exported struct fields, Java's fluent builder). Polyglot teams stop hand-translating type definitions; the schema is the single source of truth and the rest is mechanical.

Edge Cases and Limitations

Protobuf has more sharp edges than its marketing suggests. A few are worth knowing before you ship.

proto2 vs proto3 Semantics

proto3 removed optional and required labels at launch in 2016. Every singular field was implicitly optional with a fixed zero-value default, and there was no way to distinguish "field not set" from "field set to zero." This caused enough pain that optional was re-introduced in 2020 - if you mark a field optional in proto3, the generator emits a has_field() presence accessor. required is gone for good and should be considered a proto2 anti-feature: a required field can never be removed without breaking the wire format, and required-ness has surprising interactions with cross-version compatibility that bit Google internally hard enough to motivate its removal.

oneof for Unions

Use oneof when exactly one of several fields will be set. The wire format only encodes the active field, and the generated code typically exposes a discriminator (a "which case" enum in C++ and Java, a sealed class hierarchy in Kotlin, a tagged union in Rust). Inside a oneof you cannot use repeated labels or map fields; if you need either of those inside a variant, wrap them in a sub-message.

repeated Versus map

repeated T field = 1; models a list. map<K, V> field = 1; models a dictionary - but the key type K must be an integer or string scalar (not a float, not bytes, not an enum, not a message). Internally a map is sugar for repeated MapEntry where MapEntry is an auto-generated 2-field message, so wire compatibility with a manually-defined repeated-entry message is straightforward, but the parser will reject any other key type at compile time.

Reserved Field Numbers

When you delete a field, immediately add reserved 5; (or whatever the number was). The compiler will refuse to let any future field reuse that number. The cost is one line of schema; the benefit is that a future developer cannot accidentally produce silent data corruption by assigning the old number to a field with different semantics that old clients will misinterpret. You can also reserve names with reserved "old_name"; to prevent name reuse in text-format or generated-API contexts.

Defaults in proto3

proto3 fixed every default to the zero value for the type: 0 for numeric types, false for bool, empty string for string, empty bytes for bytes, the first enum value for enums. You cannot specify a custom default. This is why enums in proto3 must always have a FOO_UNSPECIFIED = 0; sentinel as the first entry - any unset enum field will read as that value, and an UNSPECIFIED sentinel makes the "I don't know" case visible at the type level rather than collapsing it into a real status.

Well-Known Types

Google ships a small library of "well-known" message types in the google.protobuf package. Timestamp and Duration handle time, Any wraps an arbitrary protobuf message with a type URL, Struct encodes a JSON-like dynamic object, FieldMask selects a subset of fields for partial updates, Empty stands in for "no payload." Import them via import "google/protobuf/timestamp.proto"; and reference them with their fully-qualified names. These types have language-specific helpers in most generated SDKs - the Go binding for Timestamp, for instance, includes .AsTime() and timestamppb.New(time.Time).

Behind the Scenes

From Google Internal to Public Open Source

Protocol Buffers were developed inside Google starting in 2001 as a replacement for an earlier internal format that had become a bottleneck for cross-service evolution. The internal version, "proto1," handled most of Google's RPC traffic for years before "proto2" was open-sourced in July 2008 under a BSD-style licence. proto3 was released in 2016 as a deliberately simpler dialect aimed at multi-language adoption outside Google. Today protobuf is one of the most widely deployed serialization formats on the planet, powering Google's own internal microservice fabric, every gRPC deployment in the wild, and a substantial fraction of high-throughput service-to-service traffic at companies including Netflix, Square, Spotify, and Shopify.

gRPC and the Protobuf Marriage

gRPC was open-sourced by Google in 2015 and adopted protobuf as its default Interface Definition Language and wire format. The pairing is so tight that the two are often conflated, but they are technically separable: gRPC's Codec interface allows alternative serialization formats, and protobuf can be sent over plain TCP, HTTP/1.1, or AMQP without any gRPC involvement. In practice, protobuf-over-gRPC is the default deployment pattern and the combination is now a CNCF graduated project hosting cross-language SDKs for Go, Java, Python, C++, Ruby, Node.js, Rust, Swift, Kotlin, Dart, and others.

Why Varint Encoding Wins for Small Numbers

Varint encoding represents an integer in 7-bit chunks, with the high bit of each byte indicating whether more bytes follow. The number 1 encodes as a single byte 0x01; the number 127 still fits in one byte; 128 takes two bytes; 16383 takes two bytes; only at 2^28 do you start paying four bytes. Real-world data is dominated by small integers - identifiers, counts, status codes, page sizes - so varints typically use 1-2 bytes per integer where fixed-width 32-bit encodings would use 4. The single counterexample is negative numbers: int32 sign-extends to 64 bits before varint-encoding, costing 10 bytes for any negative value. Use sint32 or sint64 for fields that genuinely take negative values; they apply ZigZag encoding (which maps small-magnitude negatives to small-magnitude positives) before the varint pass.

Alternative Serialization Formats

Protobuf is the most widely deployed binary format but not the only one. Apache Avro embeds the schema in the payload (or references it from a schema registry like Confluent's), enabling self-describing messages at the cost of larger payloads; it dominates the Hadoop and Kafka ecosystems. Apache Thrift (originated at Facebook) is the closest cousin to protobuf in design and predates the open-source release of proto2 by two years; it bundles its own RPC framework rather than pairing with gRPC. Cap'n Proto (designed by Kenton Varda, who also led the proto2 redesign at Google) skips the serialization step entirely - the wire format is the in-memory layout, so reads are zero-copy at the cost of larger payloads and stricter alignment requirements. FlatBuffers (also from Google) uses a similar zero-copy approach optimised for game engines and other latency-sensitive contexts where deserialization cost dominates.

Comparison: Protobuf vs JSON vs gRPC vs OpenAPI vs Cap'n Proto

The table below collects the trade-offs that matter for a real shipping decision. The numbers are typical orders of magnitude, not benchmark-precise figures - they will vary with payload shape, language runtime, and codec implementation.

Serialization format trade-offs at a glance
Format Wire size (vs JSON) Parse speed (vs JSON) Schema evolution Human-readable? Typical deployment
Protobuf0.1-0.3x5-20x fasterExcellent (field numbers + reserved)No (binary)gRPC microservices, Kafka topics
JSON1.0x (baseline)1.0x (baseline)Manual, name-basedYesBrowser APIs, configs, debugging
gRPC (uses Protobuf)0.1-0.3x5-20x fasterExcellent (inherits from protobuf)NoService-to-service RPC over HTTP/2
OpenAPI (describes JSON)1.0x1.0xManual, name-based, doc-drivenYes (the JSON it describes)REST APIs needing client codegen
Cap'n Proto0.5-1.0x50-100x faster (zero-copy)Good (struct evolution rules)NoLatency-critical IPC, game engines
Avro0.2-0.5x3-10x fasterExcellent (schema registry)NoHadoop, Kafka with registry
FlatBuffers0.5-1.0x50-100x faster (zero-copy)Good (table evolution)NoMobile games, embedded systems
Wire-size and parse-speed numbers are typical magnitudes for small-to-medium structured payloads. JSON wins on debuggability and universal tooling; protobuf wins on size, speed, and enforceable schema evolution.

The shortest decision tree: choose JSON + OpenAPI for any API a browser will call directly. Choose protobuf + gRPC for service-to-service traffic where you control both ends and care about throughput. Choose Cap'n Proto or FlatBuffers when deserialization cost dominates - game engine asset loading, embedded systems, or latency-critical IPC. Choose Avro when your data lives in Kafka and Hadoop and you want a schema registry as part of the runtime.

Frequently Asked Questions

Protobuf produces binary payloads that are typically 3 to 10 times smaller than the equivalent JSON, parses 5 to 20 times faster because field tags are integers rather than string keys, and enforces a schema at compile time so type errors surface during the build rather than at runtime. JSON wins on human readability, ubiquitous tooling, and the fact that no schema needs to be shared between client and server. Use protobuf for high-throughput service-to-service traffic where schema is already coordinated; use JSON for browser-facing APIs, configuration files, and debugging contexts where humans need to read the wire.
Choose proto3 for any new project. It is the current default at Google, gRPC requires it for some language bindings, and its smaller surface area (no required fields, no custom default values) eliminates the most common foot-guns of proto2. The one capability proto2 retains that proto3 dropped and then partially restored is field presence: in proto3, scalar fields default to zero values and you cannot distinguish unset from explicitly-zero unless you mark the field optional (re-introduced in 2020). Reach for proto2 only when maintaining legacy schemas or when you specifically need its required-field semantics for an internal system that won't evolve.
Each field on the wire is a tag-length-value or tag-value triple. The tag is a single varint that packs the field number and a 3-bit wire-type code: (field_number << 3) | wire_type. Wire type 0 is varint (used for int32, int64, bool, enum), type 1 is fixed 64-bit, type 2 is length-delimited (string, bytes, embedded message, packed repeated), type 5 is fixed 32-bit. The decoder reads the tag, looks up the wire type, consumes the appropriate number of bytes, and uses the field number to route the value into the right field in the generated message struct. Unknown field numbers are skipped rather than failing - this is the foundation of forward compatibility.
Three rules cover almost every case. First, never change the field number or wire type of an existing field - both are baked into already-deployed clients. Second, never reuse a deleted field's number; mark it with the reserved keyword so the compiler enforces the prohibition. Third, only add new fields with new numbers; old clients will skip them and new clients can read messages produced by old encoders. Renaming a field is safe at the wire level because names are not transmitted, but it breaks any text-format consumers or generated-code call sites. Changing a singular field to repeated is wire-compatible in one direction only.
Yes - the syntax is C/C++ style. Use // for single-line comments and /* … */ for block comments. Comments placed immediately above a message, field, or RPC declaration are preserved by protoc when --include_source_info is passed, allowing them to be extracted into generated documentation. Tools like protoc-gen-doc and Buf treat these as docstrings. Inline trailing comments work too but are convention-dependent for documentation extraction. Comments are stripped from the compiled binary descriptor and never travel over the wire.
The reserved keyword marks field numbers (or field names) that must never be used in the current message. Its purpose is to prevent a developer from accidentally reassigning a deleted field's number to something with different semantics - which would produce silent corruption when old clients deserialize new data into the old type. Syntax: reserved 2, 15, 9 to 11; or reserved "foo", "bar";. The protoc compiler will fail compilation if any field in the message attempts to use a reserved number or name. Reserve aggressively whenever you delete a field; the cost is one line of schema, the benefit is silent-corruption immunity.
A oneof is a tagged union: a group of fields where setting any one of them automatically clears all the others. It is protobuf's way of expressing "exactly one of these values is present." Wire-wise, only the active field is serialized, which saves space compared to encoding every field as optional. Generated code typically exposes oneof as a discriminated union or a which-case enum. Use oneof for variant types - an event that is one of LoginEvent, LogoutEvent, or PurchaseEvent, or a response that is either Result or Error. Fields inside a oneof cannot be repeated or maps.
gRPC is an RPC framework that uses protobuf as its interface definition language and default wire format. The service and rpc keywords in a .proto file define gRPC service contracts; protoc with the gRPC plugin generates client stubs and server skeletons in the target language. gRPC adds HTTP/2 transport, four streaming modes (unary, server-streaming, client-streaming, bidirectional), deadline propagation, and pluggable authentication on top of protobuf's serialization layer. You can use protobuf without gRPC (raw byte payloads over any transport) and you can technically use gRPC with a different codec via the codec interface, but the combination is overwhelmingly the default deployment pattern.