Skip to Content

Generated Code Reference (protobuf-es-v1)

Detailed reference for the TypeScript code generated by protoc-gen-pothos with protobuf-es-v1 runtime.

File Structure

For each .proto file, the plugin generates a .pb.pothos.ts file containing:

proto/example/user.proto src/__generated__/example/user.pb.pothos.ts

Each generated file includes:

  1. Import statements (with _pb suffix)
  2. Object type definitions ($Ref)
  3. Input type definitions ($Shape, $Ref)
  4. Enum type definitions
  5. Union type definitions (for oneofs)
  6. Helper functions ($toProto)

Naming Conventions

Export Identifiers

Proto DefinitionGenerated ExportType
message UserUser$RefObjectRef<User>
message User (input)UserInput$ShapeType alias
message User (input)UserInput$RefInputObjectRef<UserInput$Shape>
message User (input)UserInput$toProtoFunction
message User (partial)UserPartialInput$ShapeType alias
message User (partial)UserPartialInput$RefInputObjectRef<...>
enum RoleRole$RefEnumRef<Role, Role>
oneof content in MediaMediaContent$RefUnion type

Nested Messages

Nested messages use underscore-separated naming:

message User { message Address { string city = 1; } }

Generated exports:

  • UserAddress$Ref
  • UserAddressInput$Shape
  • UserAddressInput$Ref
  • UserAddressInput$toProto

Object Type Generation

Basic Object Type

message Date { uint32 year = 1; uint32 month = 2; uint32 day = 3; }
import { Date } from "@testapis/protobuf-es/testapis/custom_types/date_pb"; export const Date$Ref = builder.objectRef<Date>("Date"); builder.objectType(Date$Ref, { name: "Date", fields: (t) => ({ year: t.expose("year", { type: "Int", nullable: false, extensions: { protobufField: { name: "year", typeFullName: "uint32" } }, }), month: t.expose("month", { type: "Int", nullable: false, extensions: { protobufField: { name: "month", typeFullName: "uint32" } }, }), day: t.expose("day", { type: "Int", nullable: false, extensions: { protobufField: { name: "day", typeFullName: "uint32" } }, }), }), isTypeOf: (source) => { return source instanceof Date; }, extensions: { protobufMessage: { fullName: "testapis.custom_types.Date", name: "Date", package: "testapis.custom_types", }, }, });

Field Methods

MethodUsage
t.expose()Direct field exposure without resolver
t.field()Field with custom resolver

t.expose() is used when the field value can be accessed directly from the source object.

t.field() is used when any of the following conditions apply:

  • Bytes fields: Require Buffer conversion
  • Enum fields with unspecified/ignored values: Need resolver for value handling
  • Oneof fields: Access oneof member values
  • Squashed oneof union fields: Flatten nested oneof to parent type
  • Required nested message fields: Need non-null assertion (!)
  • Required repeated fields: Need non-null assertion for list

isTypeOf Implementation

protobuf-es uses instanceof for type discrimination:

isTypeOf: (source) => { return source instanceof MessageClass; }

Empty Message Type

Messages with no fields generate a noop field to satisfy GraphQL schema requirements:

export const EmptyMessage$Ref = builder.objectRef<EmptyMessage>("EmptyMessage"); builder.objectType(EmptyMessage$Ref, { name: "EmptyMessage", fields: (t) => ({ _: t.field({ type: "Boolean", nullable: true, description: "noop field", resolve: () => true, }), }), isTypeOf: (source) => { return source instanceof EmptyMessage; }, extensions: { protobufMessage: { fullName: "testapis.empty_types.EmptyMessage", name: "EmptyMessage", package: "testapis.empty_types", }, }, });

Input Type Generation

Basic Input Type

export type DateInput$Shape = { year: Date["year"]; month: Date["month"]; day: Date["day"]; }; export const DateInput$Ref: InputObjectRef<DateInput$Shape> = builder.inputRef< DateInput$Shape >("DateInput").implement({ fields: (t) => ({ year: t.field({ type: "Int", required: true, extensions: { protobufField: { name: "year", typeFullName: "uint32" } }, }), month: t.field({ type: "Int", required: true, extensions: { protobufField: { name: "month", typeFullName: "uint32" } }, }), day: t.field({ type: "Int", required: true, extensions: { protobufField: { name: "day", typeFullName: "uint32" } }, }), }), extensions: { protobufMessage: { fullName: "testapis.custom_types.Date", name: "Date", package: "testapis.custom_types", }, }, });

Shape Type

The $Shape type alias derives field types from the protobuf message type:

export type PostInput$Shape = { title: Post["title"]; // Required field publishedDate?: DateInput$Shape | null; // Optional nested message };

Partial Input Type

When partial_inputs=true is configured:

export type DatePartialInput$Shape = { year?: Date["year"] | null; month?: Date["month"] | null; day?: Date["day"] | null; }; export const DatePartialInput$Ref: InputObjectRef<DatePartialInput$Shape> = builder.inputRef<DatePartialInput$Shape>("DatePartialInput").implement({ fields: (t) => ({ year: t.field({ type: "Int", required: false, // All fields optional extensions: { protobufField: { name: "year", typeFullName: "uint32" } }, }), // ... }), // ... });

toProto Functions

protobuf-es generates $toProto helper functions for converting GraphQL input to protobuf messages.

Simple Message

export function DateInput$toProto( input: DateInput$Shape | null | undefined, ): Date { return new Date({ year: input?.year ?? undefined, month: input?.month ?? undefined, day: input?.day ?? undefined, }); }

Nested Message

export function PostInput$toProto( input: PostInput$Shape | null | undefined, ): Post { return new Post({ title: input?.title ?? undefined, publishedDate: input?.publishedDate ? DateInput$toProto(input.publishedDate) : undefined, }); }

Oneof Fields

export function OneofParentInput$toProto( input: OneofParentInput$Shape | null | undefined, ): OneofParent { return new OneofParent({ normalField: input?.normalField ?? undefined, requiredOneofMembers: input?.requiredMessage1 ? { case: "requiredMessage1", value: Message1Input$toProto(input.requiredMessage1) } : input?.requiredMessage2 ? { case: "requiredMessage2", value: Message2Input$toProto(input.requiredMessage2) } : undefined, }); }

Enum Type Generation

Basic Enum

enum MyEnum { MY_ENUM_UNSPECIFIED = 0; MY_ENUM_FOO = 1; MY_ENUM_BAR = 2; }
export const MyEnum$Ref: EnumRef<MyEnum, MyEnum> = builder.enumType("MyEnum", { values: { FOO: { value: 1, extensions: { protobufEnumValue: { name: "MY_ENUM_FOO" } }, }, BAR: { description: "This is Bar.", value: 2, extensions: { protobufEnumValue: { name: "MY_ENUM_BAR" } }, }, } as const, extensions: { protobufEnum: { name: "MyEnum", fullName: "testapi.enums.MyEnum", package: "testapi.enums", }, }, });

UNSPECIFIED Value Handling

Values matching <ENUM_NAME>_UNSPECIFIED at position 0 are excluded from GraphQL enum values.

When a field references an enum with an unspecified value, a resolver is generated:

nestedEnum: t.field({ type: ParentMessageNestedEnum$Ref, nullable: true, resolve: (source) => { if (source.nestedEnum === ParentMessage_NestedEnum.NESTED_ENUM_UNSPECIFIED) { return null; } return source.nestedEnum; }, // ... }),

IGNORED Value Handling

When an enum value is marked with the ignore option, the resolver throws an error:

prefixedEnum: t.field({ type: TestPrefixPrefixedEnum$Ref, nullable: true, resolve: (source) => { if (source.prefixedEnum === PrefixedEnum.PREFIXED_ENUM_UNSPECIFIED) { return null; } if (source.prefixedEnum === PrefixedEnum.PREFIXED_IGNORED) { throw new Error("PREFIXED_IGNORED is ignored in GraphQL schema"); } return source.prefixedEnum; }, // ... }),

Union Type Generation (Oneofs)

Basic Union

message OneofParent { oneof content { InnerMessage1 msg1 = 1; InnerMessage2 msg2 = 2; } }
export const OneofParentContent$Ref = builder.unionType( "OneofParentContent", { types: [InnerMessage1$Ref, InnerMessage2$Ref], extensions: { protobufOneof: { fullName: "testapis.oneof.OneofParent.content", name: "content", messageName: "OneofParent", package: "testapis.oneof", fields: [{ name: "msg1", type: "testapis.oneof.InnerMessage1", }, { name: "msg2", type: "testapis.oneof.InnerMessage2", }], }, }, }, );

Oneof Field Resolver

protobuf-es accesses oneof values through the oneof name property:

content: t.field({ type: OneofParentContent$Ref, nullable: true, resolve: (source) => { return source.content.value ?? null; }, extensions: { protobufField: { name: "content" } }, }),

Required Oneof

When marked with // Required.:

requiredOneofMembers: t.field({ type: OneofParentRequiredOneofMembers$Ref, nullable: false, description: "Required. disallow not_set.", resolve: (source) => { const value = source.requiredOneofMembers.value; if (value == null) { throw new Error("requiredOneofMembers should not be null"); } return value; }, extensions: { protobufField: { name: "required_oneof_members" } }, }),

Squashed Oneof Union

When a message contains only a oneof field and is marked with squashUnion: true option:

message PrefixedMessage { message SquashedMessage { oneof content { option (graphql.oneof).squash_union = true; InnerMessage oneof_field = 1 [(graphql.object_type).squash_union = true]; InnerMessage2 oneof_field_2 = 2 [(graphql.object_type).squash_union = true]; } } SquashedMessage squashed_message = 5; }

Generated union type:

export const TestPrefixPrefixedMessageSquashedMessage$Ref = builder.unionType( "TestPrefixPrefixedMessageSquashedMessage", { types: [ TestPrefixPrefixedMessageInnerMessage$Ref, TestPrefixPrefixedMessageInnerMessage2$Ref, ], extensions: { protobufOneof: { fullName: "testapis.extensions.PrefixedMessage.SquashedMessage", name: "SquashedMessage", package: "testapis.extensions", fields: [{ name: "oneof_field", type: "testapis.extensions.PrefixedMessage.InnerMessage", options: { "[graphql.object_type]": { squashUnion: true } }, }, { name: "oneof_field_2", type: "testapis.extensions.PrefixedMessage.InnerMessage2", options: { "[graphql.object_type]": { squashUnion: true } }, }], }, }, }, );

Generated field resolver:

squashedMessage: t.field({ type: TestPrefixPrefixedMessageSquashedMessage$Ref, nullable: true, resolve: (source) => { const value = source.squashedMessage?.content.value; if (value == null) { return null; } return value; }, extensions: { protobufField: { name: "squashed_message", typeFullName: "testapis.extensions.PrefixedMessage.SquashedMessage", }, }, }),

Field Resolver Patterns

Bytes Field

Bytes fields require Buffer conversion:

data: t.field({ type: "Byte", nullable: true, resolve: (source) => { return source.data == null ? null : Buffer.from(source.data); }, }),

Repeated Bytes Field

dataList: t.field({ type: ["Byte"], nullable: { list: false, items: false }, resolve: (source) => { return source.dataList.map((v) => Buffer.from(v)); }, }),

Nested Message Field

Optional nested message fields use t.expose():

optionalPrimitives: t.expose("optionalPrimitives", { type: Primitives$Ref, nullable: true, description: "Optional.", extensions: { protobufField: { name: "optional_primitives", typeFullName: "testapis.primitives.Primitives", }, }, }),

Required Nested Message Field

Required nested message fields use t.field() with non-null assertion:

requiredPrimitives: t.field({ type: Primitives$Ref, nullable: false, description: "Required.", resolve: (source) => { return source.requiredPrimitives!; }, extensions: { protobufField: { name: "required_primitives", typeFullName: "testapis.primitives.Primitives", }, }, }),

Repeated Field Nullability

For list (repeated) fields, the nullable property uses an object structure:

// Required list - list and items are never null requiredPrimitivesList: t.field({ type: [Primitives$Ref], nullable: { list: false, items: false }, description: "Required.", resolve: (source) => { return source.requiredPrimitivesList!; }, extensions: { protobufField: { name: "required_primitives_list", typeFullName: "testapis.primitives.Primitives", }, }, }), // Optional list - list can be null, but items are never null optionalPrimitivesList: t.expose("optionalPrimitivesList", { type: [Primitives$Ref], nullable: { list: true, items: false }, description: "Optional.", extensions: { protobufField: { name: "optional_primitives_list", typeFullName: "testapis.primitives.Primitives", }, }, }),
Nullable ValueGraphQL Type
{ list: false, items: false }[Type!]!
{ list: true, items: false }[Type!]

64-bit Integer Fields

protobuf-es uses the Int64 scalar for 64-bit integers:

requiredInt64Value: t.expose("requiredInt64Value", { type: "Int64", nullable: false, extensions: { protobufField: { name: "required_int64_value", typeFullName: "int64" }, }, }),

Extensions Object

Generated code includes extensions objects for introspection:

protobufMessage

extensions: { protobufMessage: { fullName: "testapis.custom_types.Date", name: "Date", package: "testapis.custom_types", }, }

protobufField

extensions: { protobufField: { name: "published_date", typeFullName: "testapis.custom_types.Date", }, }

protobufEnum

extensions: { protobufEnum: { name: "MyEnum", fullName: "testapi.enums.MyEnum", package: "testapi.enums", }, }

protobufEnumValue

extensions: { protobufEnumValue: { name: "MY_ENUM_FOO", options: { deprecated: true }, // if applicable }, }

protobufOneof

extensions: { protobufOneof: { fullName: "testapis.oneof.Media.content", name: "content", messageName: "Media", package: "testapis.oneof", fields: [ { name: "image", type: "testapis.oneof.Image" }, { name: "video", type: "testapis.oneof.Video" }, ], }, }