Description
When an OpenAPI schema declares a response with text/plain (or any non-JSON content type), openapi-fetch has all the type information needed to either:
- Require
parseAs: "text" at the type level, or
- Infer the correct
parseAs automatically at runtime from the schema
Currently, neither happens. parseAs is typed as an optional ParseAs with no relationship to the schema's response content type, and at runtime it defaults to "json" regardless. This means calling an endpoint that returns text/plain without parseAs: "text" silently compiles, then throws SyntaxError: Unexpected token at runtime when JSON.parse() is called on a plain text body.
The type gap
The generated types from openapi-typescript correctly model the content type:
// Schema declares text/plain response
responses: {
200: {
content: {
"text/plain": string; // <-- the info is right here
};
};
};
But in openapi-fetch, parseAs is unconditionally optional:
// RequestOptions — no constraint based on response content type
type RequestOptions<T> = ParamsOption<T> &
RequestBodyOption<T> & {
parseAs?: ParseAs // always optional, any value accepted
// ...
}
And ParseAsResponse only adjusts the return type based on parseAs, but never requires it:
type ParseAsResponse<T, Options> = Options extends { parseAs: ParseAs }
? BodyType<T>[Options['parseAs']]
: T // falls through to schema type, no error
So TypeScript sees data: string (correct per schema) even though the runtime will try JSON.parse() and fail.
Proposed behavior
When the schema's success response only contains non-JSON content types (e.g. text/plain, text/csv, application/octet-stream), the type system should either:
Option A (type-level enforcement): Make parseAs required and constrained to the appropriate value. For example, if the only content type is text/plain, require parseAs: "text".
Option B (runtime auto-detection): At runtime, inspect the response content type mapping from the schema (or the actual Content-Type header) and default to the appropriate parse strategy instead of always defaulting to "json".
Option A is preferable since it catches the mistake at compile time with zero runtime cost.
Impact
This is a recurring footgun. Related issues: #1883, #1165, #1284, #1486. All report the same class of runtime error (SyntaxError or Unexpected end of JSON input) when text/plain endpoints are called without parseAs. The workaround (parseAs: "text") is documented, but the whole point of using a typed client generated from an OpenAPI schema is to catch these mismatches at build time.
Environment
openapi-fetch: 0.17.0
openapi-typescript: 7.x
- TypeScript: 5.x
Proposal
- Require
parseAs: "text" at the type level, or
- Infer the correct
parseAs automatically at runtime from the schema
Option A (type-level enforcement): Make parseAs required and constrained to the appropriate value. For example, if the only content type is text/plain, require parseAs: "text".
Option B (runtime auto-detection): At runtime, inspect the response content type mapping from the schema (or the actual Content-Type header) and default to the appropriate parse strategy instead of always defaulting to "json".
Extra
Description
When an OpenAPI schema declares a response with
text/plain(or any non-JSON content type),openapi-fetchhas all the type information needed to either:parseAs: "text"at the type level, orparseAsautomatically at runtime from the schemaCurrently, neither happens.
parseAsis typed as an optionalParseAswith no relationship to the schema's response content type, and at runtime it defaults to"json"regardless. This means calling an endpoint that returnstext/plainwithoutparseAs: "text"silently compiles, then throwsSyntaxError: Unexpected tokenat runtime whenJSON.parse()is called on a plain text body.The type gap
The generated types from
openapi-typescriptcorrectly model the content type:But in
openapi-fetch,parseAsis unconditionally optional:And
ParseAsResponseonly adjusts the return type based onparseAs, but never requires it:So TypeScript sees
data: string(correct per schema) even though the runtime will tryJSON.parse()and fail.Proposed behavior
When the schema's success response only contains non-JSON content types (e.g.
text/plain,text/csv,application/octet-stream), the type system should either:Option A (type-level enforcement): Make
parseAsrequired and constrained to the appropriate value. For example, if the only content type istext/plain, requireparseAs: "text".Option B (runtime auto-detection): At runtime, inspect the response content type mapping from the schema (or the actual
Content-Typeheader) and default to the appropriate parse strategy instead of always defaulting to"json".Option A is preferable since it catches the mistake at compile time with zero runtime cost.
Impact
This is a recurring footgun. Related issues: #1883, #1165, #1284, #1486. All report the same class of runtime error (
SyntaxErrororUnexpected end of JSON input) whentext/plainendpoints are called withoutparseAs. The workaround (parseAs: "text") is documented, but the whole point of using a typed client generated from an OpenAPI schema is to catch these mismatches at build time.Environment
openapi-fetch: 0.17.0openapi-typescript: 7.xProposal
parseAs: "text"at the type level, orparseAsautomatically at runtime from the schemaOption A (type-level enforcement): Make
parseAsrequired and constrained to the appropriate value. For example, if the only content type istext/plain, requireparseAs: "text".Option B (runtime auto-detection): At runtime, inspect the response content type mapping from the schema (or the actual
Content-Typeheader) and default to the appropriate parse strategy instead of always defaulting to"json".Extra