Input (.proto schema)

Output (JSON Schema)

What this tool does

You have a Protocol Buffers schema and a service that ingests JSON — maybe a webhook handler, maybe an HTTP-transcoded gRPC gateway, maybe an API gateway doing request validation before the request hits your code. You want a JSON Schema document that mirrors the proto so you can validate incoming payloads, generate OpenAPI fragments, or feed it into structured-output prompts. This converter does that in your browser — paste the .proto, copy the JSON Schema, drop it into your validator config.

Output is JSON Schema draft 2020-12 — the current draft, supported by every modern validator including Ajv. Each message becomes an entry under $defs with type: "object" and a properties map. Each enum becomes a $defs entry with type: "string" and the value names listed under enum — matching how proto3 serialises enums by name in JSON. Field references resolve via $ref: "#/$defs/MessageName" using the leaf name, so nested types stay readable.

Type mapping follows the proto3 JSON mapping spec. string/bool map directly. 32-bit ints get type: "integer" with format: "int32"; unsigned 32-bit adds minimum: 0. 64-bit ints become type: "string" with format: "int64" and a numeric pattern, because JSON Numbers lose precision above 2^53 and proto3 encodes 64-bit integers as quoted strings on the wire. bytes becomes a string with contentEncoding: "base64". Well-known types like google.protobuf.Timestamp map to format: "date-time" per RFC 3339. Conversion runs entirely in your browser — your schema does not leave the page.

How to use it

Three steps. Output is a single JSON Schema document you can hand straight to a validator.

1

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. import directives are recognised but skipped — paste imported types inline if you need them.

Field names stay in snake_case (matching what the proto3 JSON encoder emits by default — no transformation). If your client sets preserve_proto_field_names = false, switch the property keys to camelCase by hand.

2

Read the schema

On the right: a JSON Schema 2020-12 document with $id, title, a top-level $ref pointing at your last-declared root message, and a $defs block holding every message and enum from the file. Each message becomes an object schema with a properties map; each enum becomes a string schema with the value names. Read the Understanding JSON Schema guide if you need a refresher on $ref or $defs semantics.

3

Wire it into a validator

Save the output as schema.json, load it into Ajv (Node) or jsonschema (Python) or whichever validator your stack uses, then run it against the JSON your gRPC-gateway or webhook receives. Mismatches show up as readable error paths like /items/0/sku must be string. The same schema can also feed OpenAPI 3.1 component definitions or structured-output prompts for an LLM.

When this actually saves time

Validating webhooks defined in proto

Your team uses Protobuf as the source of truth for an OrderShipped event, but the actual webhook receiver gets JSON — there is no proto runtime in the receiver. Paste the .proto, drop the JSON Schema into Ajv, and reject malformed payloads at the edge before they reach business logic. SKU-101 with a missing quantity never gets to the database.

Building OpenAPI 3.1 from gRPC schemas

You are writing OpenAPI 3.1 specs for a gRPC-gateway. OpenAPI 3.1 is JSON Schema 2020-12 compatible, so the $defs block here drops straight under components.schemas after a small renaming pass. No protoc-gen-openapi plugin to install, no Buf CLI to set up — just paste, edit, commit.

LLM structured outputs from a proto

You want OpenAI or Anthropic to return a typed Order object that matches your existing .proto. Paste the schema, take the $defs/Order entry, and pass it as the response_format JSON Schema. The model now produces output that round-trips through your gRPC service without manual coercion.

Reviewing a Protobuf API change

A backend teammate added two fields to Address and renamed an enum value. You want to see how that affects the JSON Schema your gateway uses without running the full codegen pipeline. Paste the new .proto, diff the schema against your committed copy, leave a focused review comment on the PR.

Common questions

Is my schema sent anywhere?

No. The parser and JSON Schema emitter run entirely in your browser as JavaScript. Open DevTools and watch the Network tab while you paste — zero requests. Useful when your schema includes internal package paths, type names, or anything you would not want to hand to a third-party service.

Which JSON Schema draft does the output target?

Draft 2020-12, the current published draft. The $schema URI on every output document is https://json-schema.org/draft/2020-12/schema. See the 2020-12 release notes for what changed since 2019-09. Every actively maintained validator (Ajv 8+, jsonschema 4+ for Python, NJsonSchema, Justify for Java) supports 2020-12 by default.

Why are int64 fields strings, not integers?

Because that is what the proto3 JSON mapping spec says, and it is right. JSON Numbers are IEEE-754 doubles, which lose precision above 2^53. A real int64 can carry values way past that ceiling — order IDs, timestamps in nanos, ledger balances — so proto3 encodes 64-bit integers as quoted JSON strings. The schema reflects that with type: "string", format: "int64", and a numeric pattern so a validator still rejects "abc". If your server hands out 64-bit ints as raw JSON numbers (some legacy gateways do), change those entries to { "type": "integer" } by hand.

Why are enums strings, not integers?

Same reason — that is the proto3 JSON encoding default. Enums serialise as their value name ("ORDER_STATUS_PAID") rather than the integer wire number (2). That makes JSON payloads readable and the schema simpler. The integer numbers are not in the JSON Schema because they are a wire-format concern, not a validation concern. If you have a non-default encoder configured to emit ints, swap type: "string" for type: "integer" on the enum entry.

How does it handle map<K, V>?

Renders as { "type": "object", "additionalProperties": <V-schema> }. JSON object keys are always strings, so a proto map with a non-string key (e.g. map<int32, string>) gets a description note explaining that the runtime keys will be string-coerced. The value schema follows the same type-mapping rules as a regular field.

Are fields marked required?

No — proto3 fields always have a default in the wire format and are always present in the JSON output (with empty defaults like "", 0, false, [], {}), so the schema does not list anything under required. If you actually want a field to be required at validation time, add it to a required array on the parent message by hand. proto3 optional fields and oneof are not enforced as oneOf in the output either — those are runtime semantics that JSON Schema cannot fully express without extra annotations.

How are nested messages referenced?

Every message and enum is hoisted to a flat $defs block keyed by its leaf name. Field references go through $ref: "#/$defs/MessageName". Flattening keeps the document compact and means nested-twice types are not duplicated. If two messages in different packages share a leaf name, the converter keeps the first definition — split conflicting names before pasting if that matters.

Can I plug this straight into Ajv?

Yes. ajv.compile(schema) on the output works out of the box once Ajv is configured for draft 2020-12 (new Ajv2020() from ajv/dist/2020). The $ref entries resolve internally because everything is in the same document. If you want format validation (date-time, duration), add ajv-formats alongside.

Related tools

If you are working with Protobuf, JSON Schema, and validation, these pair well: