Skip to content

Question: Can we leverage paths’ per-status response types to narrow data/error after branching? #2392

@hajiung25

Description

@hajiung25

openapi-typescript version

7.8.0

Node.js version

v23.7.0

OS + version

macOS 15.5

Description

Hello, I’m using openapi-react-query with an OpenAPI 3.1 spec.

Some endpoints define multiple response statuses (201, 400, 422, etc.), each with a different schema. In the generated hooks (useQuery / useMutation), all possible response types are returned as a union, and TypeScript does not narrow the type automatically based on the status code.

Below is a portion of my /account/signup spec:

paths:
  /account/signup:
    post:
      tags: ["Account"]
      summary: Signup
      description: Signup request
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupReq"
      responses:
        "201":
          description: Signup success
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MsgRes"
        "400":
          description: Signup failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMsgRes"
        "422":
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorRes"

And here is an abridged shape of the generated TypeScript types from openapi-typescript:

export interface components {
  schemas: {
    SignupReq: { /* ...fields... */ };
    MsgRes: { success: boolean; message: string };
    ErrorMsgRes: { success: boolean; message: string };
    ValidationErrorRes: {
      success: false;
      message: string;
      error: {
        issues: Array<{ code: string; path: Array<string|number>; message: string }>;
        name: string;
      };
    };
  };
}

export interface paths {
  "/account/signup": {
    post: {
      requestBody: {
        content: {
          "application/json": components["schemas"]["SignupReq"];
        };
      };
      responses: {
        201: { content: { "application/json": components["schemas"]["MsgRes"] } };
        400: { content: { "application/json": components["schemas"]["ErrorMsgRes"] } };
        422: { content: { "application/json": components["schemas"]["ValidationErrorRes"] } };
      };
    };
  };
}

In practice with openapi-fetch/openapi-react-query, the data remains a union:

const res = await client.POST("/account/signup", { body });
// res.data is typed as MsgRes | ErrorMsgRes | ValidationErrorRes,
// so even after checking res.response.status, TS doesn't narrow automatically.
if (res.response.status === 201) {
  // I'd like res.data to be inferred as MsgRes here.
}

What I’m looking for

  • Status-based automatic type narrowing (ideally a discriminated union keyed by status), e.g.:
if (res.response.status === 201) {
  // data: MsgRes
} else if (res.response.status === 400) {
  // error: ErrorMsgRes
} else if (res.response.status === 422) {
  // error: ValidationErrorRes
}

Questions

  1. Is there a way to extend/implement this status-based narrowing in the current library?
    (e.g., plugin, generic overrides, codegen options, etc.)
  2. If this approach is not recommended, what is the suggested usage pattern for handling multiple response statuses?
  • e.g., normalizing all error responses into a common error type, or avoiding status-based type enforcement and handling it at runtime.

Environment:

  • OpenAPI: 3.1.0
  • Libraries:
    • "openapi-fetch": "^0.14.0",
    • "openapi-react-query": "^0.5.0",
    • "openapi-typescript": "^7.8.0",

Reproduction

Expected result

Required

  • My OpenAPI schema is valid and passes the Redocly validator (npx @redocly/cli@latest lint)

Extra

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestopenapi-tsRelevant to the openapi-typescript library

    Type

    No type

    Projects

    Status

    Accepted

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions