Skip to content

Enum definitions (codegen)

Package: @reharik/graphql-codegen-smart-enum

A GraphQL Code Generator plugin that turns your schema's enum types into @reharik/smart-enum definitions. You define enums in SDL; codegen produces type-safe smart-enum objects with lookup methods, display strings, and full inference — no hand-authored enum files to keep in sync with the schema.

Why you want this

If your enums live in a GraphQL schema, hand-writing matching smart-enum definitions is busywork that rots: every new schema value is a second edit somewhere else, and the day someone forgets, your types and your schema disagree and nobody notices until runtime. This plugin makes the schema the single source of truth. Run codegen and the definitions — keys, wire values, display strings from descriptions, deprecation flags — are derived from the SDL you already maintain. Add a value to the schema, regenerate, done. The enums can't drift from the schema because they're produced from it.

What it generates

Given this schema:

graphql
"""
Payment processing status
"""
enum PaymentStatus {
  """Waiting for payment"""
  PENDING
  """Payment completed successfully"""
  PAID
  """Payment was canceled"""
  CANCELED @deprecated(reason: "Use VOIDED")
  """Payment was voided"""
  VOIDED
}

enum SortDirection {
  ASC
  DESC
}

the plugin emits:

typescript
import { enumeration, type Enumeration } from '@reharik/smart-enum';

const paymentStatusInput = {
  pending: { display: 'Waiting for payment' },
  paid: { display: 'Payment completed successfully' },
  canceled: {
    display: 'Payment was canceled',
    deprecated: true,
    deprecationReason: 'Use VOIDED',
  },
  voided: { display: 'Payment was voided' },
} as const;

const sortDirectionInput = ['asc', 'desc'] as const;

export type PaymentStatus = Enumeration<typeof PaymentStatus>;
export type SortDirection = Enumeration<typeof SortDirection>;

export const PaymentStatus = enumeration<typeof paymentStatusInput>(
  'PaymentStatus',
  { input: paymentStatusInput },
);
export const SortDirection = enumeration<typeof sortDirectionInput>(
  'SortDirection',
  { input: sortDirectionInput },
);

Enum values with descriptions get object input with display metadata. Plain enums without descriptions or deprecations get the compact array form. Deprecated values always force object input so the deprecated flag survives.

All member keys are camelCased from the GraphQL value name (IN_REVIEWinReview). If camelCasing causes a collision within an enum, codegen fails with a clear error.

Install

bash
npm install @reharik/smart-enum
npm install -D @reharik/graphql-codegen-smart-enum @graphql-codegen/cli graphql

@reharik/smart-enum is a runtime dependency (generated files import it). The plugin and CLI are dev-only.

Configuration

typescript
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    // Standard TypeScript types (optional)
    './src/generated/graphql-types.ts': {
      plugins: ['typescript'],
    },
    // Smart-enum definitions
    './src/generated/graphql-smart-enums.ts': {
      plugins: ['@reharik/graphql-codegen-smart-enum'],
      config: {
        emitDescriptionsAsDisplay: true,
      },
    },
  },
};

export default config;

Options

OptionTypeDefaultDescription
emitDescriptionsAsDisplaybooleantrueUse GraphQL enum value descriptions as the display field. When false, only enums with deprecated values or @enumMeta directives get object input.
enumClassSuffixstring''Suffix appended to generated enum names (e.g. 'Enum'PaymentStatusEnum).
skipEnumsstring[]GraphQL enum type names to exclude from output.
externalEnumsRecord<string, string>Map of GraphQL enum type names to import paths for hand-authored enums. Each named enum must also appear in skipEnums. See Hand-authored enums.

Hand-authored enums

Sometimes you want to hand-author an enum — to add custom methods, derive props at runtime, or wrap a third-party value object. List those names in skipEnums so the plugin doesn't generate them. But the generated enumRegistry barrel still needs to include them, otherwise the server-side patchSchemaEnumSerializers can't find them when GraphQL calls parseValue on a request argument — and the resolver receives a raw string instead of a member.

externalEnums bridges the gap:

yaml
config:
  skipEnums:
    - ReactionEmoji
    - ViewerOperation
  externalEnums:
    ReactionEmoji: '../hand-authored/reactions'
    ViewerOperation: '../hand-authored/viewerOperations'

The plugin emits imports for each hand-authored enum and includes them in the registry:

typescript
import { ReactionEmoji } from '../hand-authored/reactions';
import { ViewerOperation } from '../hand-authored/viewerOperations';

// ... generated enums ...

export const enumRegistry = {
  // ... generated enums ...
  ReactionEmoji,
  ViewerOperation,
} as const;

The registry key is always the GraphQL type name. The plugin does not re-export hand-authored enums as named exports — consumers keep importing them from their original location.

Local development

When developing the plugin itself, reference the built output directly:

typescript
generates: {
  './src/generated/graphql-smart-enums.ts': {
    plugins: ['./path/to/dist/index.js'],
  },
}

Next

Released under the MIT License.