Protobuf to OpenAPI Converter
Paste a .proto schema. Get an OpenAPI 3.1 YAML document with the messages and enums emitted as components.schemas — ready to paste into your spec.
Input (.proto schema)
Output (OpenAPI YAML)
What this tool does
You have a Protocol Buffers schema and a frontend, partner, or QA team that wants an OpenAPI document for the same shapes — usually because the gRPC service is also exposed over HTTP via something like grpc-gateway. Setting up protoc-gen-openapiv2 in your build is the right answer for production, but it is overkill when you just need a quick spec to share or to drop into a Swagger UI. This converter does the schema half of the job in your browser — paste the .proto, copy the YAML, you have valid OpenAPI components.
Output is a minimal but valid OpenAPI 3.1.0 document with an empty paths: {} and one entry per Protobuf message or enum under components.schemas. We pick OpenAPI 3.1 specifically because its data model aligns with JSON Schema 2020-12 — that is the version where formats like int64, date-time, and duration are first-class and where $ref can sit alongside other keywords without the old 3.0 rewriting hacks.
Type mapping follows the proto3 JSON mapping: 32-bit ints become type: integer, format: int32, 64-bit ints become type: string, format: int64 with a numeric pattern (because JSON numbers above 2^53 lose precision), repeated T becomes an array, map<K, V> becomes an object with additionalProperties, and field names stay snake_case so the schema matches what your server actually serialises. Enums emit as type: string with an enum list of value names — same as proto3 JSON. The converter runs entirely in your browser; nothing about your schema leaves the page.
How to use it
Three steps. Output is a YAML document you can drop into a Swagger UI or merge into an existing spec.
Paste your .proto schema
Drop the schema into the left editor. syntax = "proto3"; at the top is fine but optional. The parser handles nested message blocks, enum declarations, oneof, map<K, V>, and field options. Cross-file import statements are recognised but skipped — paste imported types inline if your schemas reference each other.
Field names stay snake_case in the output, matching the proto3 JSON encoder default. If your gateway is configured to camelCase JSON names, find-replace in the output or change the gateway setting.
Read the OpenAPI output
On the right: a YAML document with openapi: 3.1.0, an info block, an empty paths: {}, and your messages and enums under components.schemas. Nested message references use $ref: '#/components/schemas/MessageName' against the leaf name, so flat references work even when your .proto declares nested types.
Wire it up
Drop the file into a Swagger UI or Redoc instance to see how the schemas render. To turn it into a complete spec, add real paths entries pointing at your gRPC-gateway routes (or hand-write them) — $ref them at #/components/schemas/Order and you are off.
When this actually saves time
Sharing a schema with a frontend or partner team
A frontend team is consuming your gRPC-transcoded service. They asked for an OpenAPI doc so they can plug it into their type generator or API explorer. Paste the .proto, copy the YAML, send it over. They get the schema in a format their tooling already understands.
Bootstrapping the spec for a new HTTP-transcoded API
You are spinning up a new service that will run as both gRPC and HTTP via grpc-gateway with buf. The components/schemas section is mechanical — generate it from this tool, then hand-write the paths with the right route templates. Faster than scaffolding the whole thing by hand.
Keeping a Swagger UI in sync with .proto changes
Your team uses Swagger UI for the human-readable API doc, but the source of truth lives in .proto files. Backend teammate adds shipping_address to Order; you regenerate the schemas section here, paste it back into the spec, ship the doc update.
Sanity-checking codegen output
You ran protoc-gen-openapiv2 as part of your build and got a 4000-line YAML file. To verify a specific message is shaped right, paste just that one .proto file here and compare. Quick reference for what a clean OpenAPI 3.1 mapping should look like.
Common questions
Why OpenAPI 3.1 and not 3.0?
OpenAPI 3.1 aligns its data model with JSON Schema 2020-12, which means formats like int64, date-time, and duration are well defined and you can put $ref alongside other keywords without the awkward workarounds that 3.0 needed. Most modern tooling (Redoc, Swagger UI 5, Stoplight, Spectral) speaks 3.1 fine. If you genuinely need 3.0 output for legacy tooling, change openapi: 3.1.0 at the top of the document — the rest is compatible enough that it usually still validates.
Why is int64 emitted as a string, not a number?
JSON numbers are IEEE-754 doubles in practice — they lose precision above 2^53. The official proto3 JSON mapping says int64, uint64, fixed64, sfixed64, and sint64 must be encoded as JSON strings. So the OpenAPI schema uses type: string, format: int64, pattern: "^-?[0-9]+$" to match what the server actually sends. If you want to assume small values and use type: integer instead, find-replace in the output.
Why is paths empty?
.proto files describe messages and services, not HTTP routes. The HTTP transcoding (path, method, query, body) is configured separately — usually via google.api.http annotations or grpc-gateway options. We do not parse those, so we leave paths: {} for you to fill in. The components/schemas part is the reusable, mechanical half — that is what this tool gives you.
Why are field names snake_case?
Because that is what proto3 JSON encodes by default. order_id in the .proto serialises as "order_id" in the JSON wire format unless you turn on the camelCase option (preserve_proto_field_names in some encoders, or the gateway-side flag). Keeping the OpenAPI schema in snake_case means it matches the JSON your server actually sends. If your stack does camelCase, run the output through a find-replace.
How are nested messages handled?
Each Protobuf message becomes a top-level entry under components.schemas keyed by its leaf name (Address, not commerce.v1.Address). References use $ref: '#/components/schemas/Address'. If two messages have the same leaf name in different packages, the second one will overwrite the first — split them across separate documents or rename in the .proto.
Does it understand google.protobuf well-known types?
A few of them. google.protobuf.Timestamp renders as type: string, format: date-time; Duration as type: string, format: duration; Empty as an empty object; Any as a generic object; Value as {} (any JSON value). For other Google well-known types, the converter currently emits a $ref to the leaf name — you can either define those schemas yourself or replace the references with the right primitive type.
Related tools
If you are working with Protobuf and OpenAPI/JSON Schema, these pair well: