Skip to Content
📖 Guide DocumentsProtocol BufferMessage Schema

typia.protobuf.message — emit a .proto schema from a TypeScript type

If you only encode and decode within TypeScript, you don’t need this — the encode and decode functions analyze your type internally and produce a wire-compatible binary form.

typia.protobuf.message<T>() is for the other case: when you need to share the schema with a service written in another language (Go, Python, …). It returns the .proto text that any Protocol Buffer compiler can consume.

signature
namespace protobuf { function message<T>(): string; // returns .proto schema text }

First example

hello-message.ts
import typia, { tags } from "typia"; interface User { id: string; age: number & tags.Type<"uint32">; hobbies: string[]; } const proto: string = typia.protobuf.message<User>(); console.log(proto); // // syntax = "proto3"; // message User { // required string id = 1; // required uint32 age = 2; // repeated string hobbies = 3; // }
examples/src/protobuf/message.ts
import typia, { tags } from "typia"; typia.protobuf.message<IMember>(); interface IMember { id: | (string & tags.Sequence<11>) | (number & tags.Type<"uint64"> & tags.Sequence<12>) | (Uint8Array & tags.Sequence<13>); name: (string & tags.Sequence<20>) | null; children: Array<IMember> & tags.Sequence<30>; keywords: Map<string, string> & tags.Sequence<40>; thumbnail: | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">) | Uint8Array; email: string & tags.Format<"email">; hobbies: Array<IHobby>; } interface IHobby { id: string & tags.Format<"uuid">; name: string; valid: boolean; }

Numeric scalar types

Protocol Buffer has narrower numeric types than TypeScript (int32, uint32, int64, uint64, float, double). Use type tags to pick the protobuf scalar type explicitly:

type Score = number & tags.Type<"float">; type Population = bigint & tags.Type<"uint64">; type Mixed = (number & tags.Type<"int32">) | (bigint & tags.Type<"uint64">);

If you don’t tag a number, typia treats it as double. If you don’t tag a bigint, typia treats it as int64.

tags.Type<"int8"> and tags.Type<"int16"> validate the smaller signed integer ranges, then emit the protobuf int32 scalar type. tags.Type<"uint8"> and tags.Type<"uint16"> validate the smaller unsigned integer ranges, then emit uint32. Protocol Buffer has no distinct 8-bit or 16-bit numeric scalar types.

The TypeScript compiler will block invalid combinations — you can’t put tags.Type<"uint32"> on a bigint, for example.

examples/src/protobuf/message-type-tag.ts
import typia, { tags } from "typia"; interface TypeTagExample { // ATOMIC TYPES int32: number & tags.Type<"int32">; uint32: number & tags.Type<"uint32">; uint64: bigint & tags.Type<"uint64">; int64: number & tags.Type<"int64">; float: number & tags.Type<"float">; double: number | undefined; string: string | null; // UNION TYPES uint32_or_double: number & (tags.Type<"uint32"> | tags.Type<"double">); int32_or_uint64: | (number & tags.Type<"int32">) | (bigint & tags.Type<"uint64">); int32_or_float_or_uint64: | (number & (tags.Type<"int32"> | tags.Type<"float">)) | (bigint & tags.Type<"uint64">); // ARRAY AND MAP uint64_array: Array<bigint & tags.Type<"uint64">>; int32_map?: Map<number & tags.Type<"int32">, string> | null; } //---- // PROTOBUF MESSAGE SCHEMA //---- typia.protobuf.message<TypeTagExample>(); //---- // DECODE FUNCTION //---- typia.protobuf.createDecode<TypeTagExample>(); //---- // ENCODE FUNCTION //---- typia.protobuf.createEncode<TypeTagExample>();

Comment tags

The same scalar choices are also expressible as @type comments:

interface User { /** @type uint8 */ priority: number; /** @type uint32 */ age: number; }
Caution

Same caveats as validators/tags: comment tags are not type-checked (a typo is ignored, so protobuf falls back to the default double scalar for an untagged number), they only work on object properties, and they can’t express union numeric types. Prefer type tags unless you’re maintaining a JSDoc-style codebase.

examples/src/protobuf/message-comment-tag.ts
import typia from "typia"; export interface CommentTagExample { /** @type int32 */ int32: number; /** @type uint32 */ uint32?: number | null; /** @type uint64 */ uint64?: number; /** @type int64 */ int64: number; /** @type float */ float: number | null; double: number; string: string; } //---- // PROTOBUF MESSAGE SCHEMA //---- typia.protobuf.message<CommentTagExample>(); //---- // DECODE FUNCTION //---- typia.protobuf.createDecode<CommentTagExample>(); //---- // ENCODE FUNCTION //---- typia.protobuf.createEncode<CommentTagExample>();

Restrictions

Protocol Buffer’s type system is much narrower than TypeScript’s. The following are not representable:

Top-level type must be a single static object

number, Array<T>, Cat | Dog, Record<string, T> — none of these are valid top-level shapes. Wrap them in an object.

import typia from "typia"; interface Cat { type: "cat"; name: string; ribbon: boolean } interface Dog { type: "dog"; name: string; hunt: boolean } typia.protobuf.message<bigint>(); typia.protobuf.createDecode<Record<string, number>>(); typia.protobuf.createDecode<Map<number & typia.tags.Type<"float">, Dog>>(); typia.protobuf.createEncode<boolean[]>(); typia.protobuf.createEncode<Cat | Dog>();

Container nesting is limited

Array<T>, Map<K, V>, and Record<string, T> are containers. The rules:

  • No two-dimensional containers. number[][] is not allowed. Neither is Map<string, number[]>.
  • No unions inside container values. Map<string, Cat | Dog> is not allowed.
  • Map keys must be atomic and non-union. Map<Cat, string> and Map<string | number, Dog> are not allowed.
import typia from "typia"; interface IPointer<T> { value: T } interface Cat { type: "cat"; name: string; ribbon: boolean } interface Dog { type: "dog"; name: string; hunt: boolean } typia.protobuf.message<IPointer<number[][]>>(); typia.protobuf.createEncode<IPointer<Record<string, string[]>>>(); typia.protobuf.createDecode<IPointer<Map<string, Cat | Dog>>>(); typia.protobuf.message<IPointer<Map<Cat, string>>>(); typia.protobuf.message<IPointer<Map<number | string, Dog>>>();

Types with no Protocol Buffer counterpart

These are rejected outright:

  • any / unknown (you don’t know how to encode an unknown value)
  • function types
  • Set<T>, WeakSet<T>, WeakMap<K, V> — use Array<T> / Map<K, V> instead
  • Date, Boolean, BigInt, Number, String — use the primitive (string, boolean, …) instead
  • Typed arrays except Uint8Array — use Uint8Array for binary data
import typia from "typia"; interface Something { any: any; unknown: unknown; closure: () => void; dict: Set<string> | WeakSet<Something> | WeakMap<Something, string>; date: Date; classic: String; buffer: ArrayBuffer; } typia.protobuf.message<Something>();

Where to go next

Last updated on