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; }

Wire types

Protocol Buffer has narrower numeric types than TypeScript (int32, uint32, int64, uint64, float, double). Use type tags to pick the wire 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.

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

Same wire types are also expressible as @type comments:

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

Same caveats as validators/tags: comment tags are not type-checked (a typo silently falls back to double), 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