Skip to Content

Generated Code Reference (protobuf-es)

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

Overview

protobuf-es v2 introduces a Schema-based API where messages are plain objects with associated Schema descriptors. The key differences from v1:

Featureprotobuf-es-v1protobuf-es (v2)
Message typeClassPlain object + Schema
Type checkinstanceofisMessage(source, Schema)
Message creationnew Message({...})create(Schema, {...})
Import patternimport { User } from "...user_pb"import { User, UserSchema } from "...user_pb"

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, including Schema symbols)
  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 Primitives { // Required. int32 required_int32_value = 1; // Required. string required_string_value = 2; // Required. bool required_bool_value = 3; }
import { Primitives, PrimitivesSchema } from "@example/proto/primitives_pb"; import { isMessage } from "@bufbuild/protobuf"; export const Primitives$Ref = builder.objectRef<Primitives>("Primitives"); builder.objectType(Primitives$Ref, { name: "Primitives", fields: (t) => ({ requiredInt32Value: t.expose("requiredInt32Value", { type: "Int", nullable: false, extensions: { protobufField: { name: "required_int32_value", typeFullName: "int32" }, }, }), requiredStringValue: t.expose("requiredStringValue", { type: "String", nullable: false, extensions: { protobufField: { name: "required_string_value", typeFullName: "string" }, }, }), requiredBoolValue: t.expose("requiredBoolValue", { type: "Boolean", nullable: false, extensions: { protobufField: { name: "required_bool_value", typeFullName: "bool" }, }, }), }), isTypeOf: (source) => { return isMessage(source, PrimitivesSchema); }, extensions: { protobufMessage: { fullName: "example.Primitives", name: "Primitives", package: "example", }, }, });

isTypeOf Implementation

protobuf-es v2 uses isMessage() function with Schema for type discrimination:

import { isMessage } from "@bufbuild/protobuf"; import { UserSchema } from "./user_pb"; isTypeOf: (source) => { return isMessage(source, UserSchema); }

This is different from v1 which uses instanceof:

// v1 (for comparison) isTypeOf: (source) => { return source instanceof User; }

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

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 isMessage(source, EmptyMessageSchema); }, extensions: { protobufMessage: { fullName: "example.EmptyMessage", name: "EmptyMessage", package: "example", }, }, });

Input Type Generation

Basic Input Type

export type PrimitivesInput$Shape = { requiredInt32Value: Primitives["requiredInt32Value"]; requiredStringValue: Primitives["requiredStringValue"]; requiredBoolValue: Primitives["requiredBoolValue"]; }; export const PrimitivesInput$Ref: InputObjectRef<PrimitivesInput$Shape> = builder.inputRef<PrimitivesInput$Shape>("PrimitivesInput").implement({ fields: (t) => ({ requiredInt32Value: t.field({ type: "Int", required: true, extensions: { protobufField: { name: "required_int32_value", typeFullName: "int32" }, }, }), requiredStringValue: t.field({ type: "String", required: true, extensions: { protobufField: { name: "required_string_value", typeFullName: "string" }, }, }), requiredBoolValue: t.field({ type: "Boolean", required: true, extensions: { protobufField: { name: "required_bool_value", typeFullName: "bool" }, }, }), }), extensions: { protobufMessage: { fullName: "example.Primitives", name: "Primitives", package: "example", }, }, });

Shape Type

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

export type MessageInput$Shape = { requiredPrimitives: PrimitivesInput$Shape; // Required nested message optionalPrimitives?: PrimitivesInput$Shape | null; // Optional nested message };

Partial Input Type

When partial_inputs=true is configured:

export type PrimitivesPartialInput$Shape = { requiredInt32Value?: Primitives["requiredInt32Value"] | null; requiredStringValue?: Primitives["requiredStringValue"] | null; requiredBoolValue?: Primitives["requiredBoolValue"] | null; }; export const PrimitivesPartialInput$Ref: InputObjectRef<PrimitivesPartialInput$Shape> = builder.inputRef<PrimitivesPartialInput$Shape>("PrimitivesPartialInput").implement({ fields: (t) => ({ requiredInt32Value: t.field({ type: "Int", required: false, // All fields optional extensions: { protobufField: { name: "required_int32_value", typeFullName: "int32" }, }, }), // ... }), // ... });

toProto Functions

protobuf-es v2 uses create() function with Schema for message creation. The generated $toProto helper functions convert GraphQL input to protobuf messages.

Simple Message

import { create } from "@bufbuild/protobuf"; import { PrimitivesSchema } from "./primitives_pb"; export function PrimitivesInput$toProto( input: PrimitivesInput$Shape | null | undefined, ): Primitives { return create(PrimitivesSchema, { requiredInt32Value: input?.requiredInt32Value ?? undefined, requiredStringValue: input?.requiredStringValue ?? undefined, requiredBoolValue: input?.requiredBoolValue ?? undefined, }); }

This is different from v1 which uses class constructor:

// v1 (for comparison) export function PrimitivesInput$toProto( input: PrimitivesInput$Shape | null | undefined, ): Primitives { return new Primitives({ requiredInt32Value: input?.requiredInt32Value ?? undefined, // ... }); }

Nested Message

export function MessageInput$toProto( input: MessageInput$Shape | null | undefined, ): Message { return create(MessageSchema, { requiredPrimitives: input?.requiredPrimitives ? PrimitivesInput$toProto(input.requiredPrimitives) : undefined, optionalPrimitives: input?.optionalPrimitives ? PrimitivesInput$toProto(input.optionalPrimitives) : undefined, requiredPrimitivesList: input?.requiredPrimitivesList?.map((v) => PrimitivesInput$toProto(v) ), optionalPrimitivesList: input?.optionalPrimitivesList?.map((v) => PrimitivesInput$toProto(v) ), }); }

Oneof Fields

Oneof fields use the { case, value } format:

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

Enum Type Generation

Basic Enum

enum MyEnum { MY_ENUM_UNSPECIFIED = 0; MY_ENUM_FOO = 1; MY_ENUM_BAR = 2; }
import { MyEnum } from "./enums_pb"; import { EnumRef } from "@pothos/core"; 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:

requiredMyEnum: t.field({ type: MyEnum$Ref, nullable: false, description: "Required.", resolve: (source) => { if (source.requiredMyEnum === MyEnum.UNSPECIFIED) { throw new Error( "requiredMyEnum is required field. But got unspecified.", ); } return source.requiredMyEnum; }, extensions: { protobufField: { name: "required_my_enum", typeFullName: "testapi.enums.MyEnum", }, }, }), optionalMyEnum: t.field({ type: MyEnum$Ref, nullable: true, description: "Optional.", resolve: (source) => { if (source.optionalMyEnum === MyEnum.UNSPECIFIED) { return null; // nullable field returns null for unspecified } return source.optionalMyEnum; }, // ... }),

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 required_oneof_members { OneofMemberMessage1 required_message1 = 1; OneofMemberMessage2 required_message2 = 2; } }
export const OneofParentRequiredOneofMembers$Ref = builder.unionType( "OneofParentRequiredOneofMembers", { types: [OneofMemberMessage1$Ref, OneofMemberMessage2$Ref], description: "Required. disallow not_set.", extensions: { protobufOneof: { fullName: "testapis.oneof.OneofParent.required_oneof_members", name: "required_oneof_members", messageName: "OneofParent", package: "testapis.oneof", fields: [{ name: "required_message1", type: "testapis.oneof.OneofMemberMessage1", }, { name: "required_message2", type: "testapis.oneof.OneofMemberMessage2", }], }, }, }, );

Oneof Field Resolver

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

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" } }, }), optionalOneofMembers: t.field({ type: OneofParentOptionalOneofMembers$Ref, nullable: true, resolve: (source) => { return source.optionalOneofMembers.value; // returns undefined if not set }, extensions: { protobufField: { name: "optional_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 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:

requiredBytesValue: t.field({ type: "Byte", nullable: false, resolve: (source) => { return Buffer.from(source.requiredBytesValue); }, extensions: { protobufField: { name: "required_bytes_value", typeFullName: "bytes" }, }, }),

Repeated Bytes Field

requiredBytesValues: t.field({ type: ["Byte"], nullable: { list: false, items: false }, resolve: (source) => { return source.requiredBytesValues.map((v) => Buffer.from(v)); }, extensions: { protobufField: { name: "required_bytes_values", typeFullName: "bytes" }, }, }),

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" }, }, }),

Supported 64-bit types: int64, uint64, sint64, fixed64, sfixed64

Import Patterns

Generated File Imports

// @generated by protoc-gen-pothos v0.6.2 with parameter "protobuf_lib=protobuf-es,..." import { builder } from "../../builder"; // Import message types AND Schema symbols import { User, UserSchema, Address, AddressSchema, } from "@example/proto/user_pb"; // Import create and isMessage from @bufbuild/protobuf import { create, isMessage } from "@bufbuild/protobuf"; // Import Pothos types import { InputObjectRef, EnumRef } from "@pothos/core";

Key Import Differences from v1

Importv1v2
Message typeimport { User } from "...user_pb"import { User, UserSchema } from "...user_pb"
Type utilitiesN/Aimport { create, isMessage } from "@bufbuild/protobuf"

Extensions Object

Generated code includes extensions objects for introspection:

protobufMessage

extensions: { protobufMessage: { fullName: "testapis.primitives.Primitives", name: "Primitives", package: "testapis.primitives", }, }

protobufField

extensions: { protobufField: { name: "required_primitives", typeFullName: "testapis.primitives.Primitives", }, }

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.OneofParent.required_oneof_members", name: "required_oneof_members", messageName: "OneofParent", package: "testapis.oneof", fields: [ { name: "required_message1", type: "testapis.oneof.OneofMemberMessage1" }, { name: "required_message2", type: "testapis.oneof.OneofMemberMessage2" }, ], }, }