Skip to content

feat: support multiple content types in requestBody#91

Merged
stijnvanhulle merged 21 commits intomainfrom
claude/multi-content-type-request-kmujk
May 4, 2026
Merged

feat: support multiple content types in requestBody#91
stijnvanhulle merged 21 commits intomainfrom
claude/multi-content-type-request-kmujk

Conversation

@stijnvanhulle
Copy link
Copy Markdown
Contributor

@stijnvanhulle stijnvanhulle commented Apr 26, 2026

Summary

Implements issue #80 and the plan in docs/plans/multi-content-type-request-body.md.

When an OpenAPI operation declares multiple content types for its requestBody (e.g. application/json and multipart/form-data), all three plugins now generate proper support for every content type instead of silently ignoring all but the first.

  • plugin-ts: emits individual types per content type (UploadFileJsonData, UploadFileFormData) followed by a union alias (type UploadFileData = UploadFileJsonData | UploadFileFormData). Single content type output is unchanged.
  • plugin-client: adds a contentType parameter (literal union + default from first declared type) and uses a runtime ternary to dispatch between form-data and JSON paths. hasFormData now checks all content entries via .some() instead of only content[0].
  • plugin-react-query: contentType is forwarded through mutation variables into the client call; buildFormData import is now conditional on the operation actually using multipart/form-data.
  • internals/tanstack-query: buildMutationArgParams accepts an optional extraBodyParams array so callers can inject additional body-level params (used for contentType).

All single-content-type operations are backwards-compatible — generated output is identical to before.

TODO

  • move contentType to config

When an OpenAPI operation declares more than one content type for its
requestBody (e.g. application/json + multipart/form-data), plugins now
generate proper support for all of them instead of silently using only
the first entry.

Changes:
- plugin-ts/typeGenerator: emits individual types per content type
  (e.g. UploadFileJsonData, UploadFileFormData) plus a union alias
  (UploadFileData = UploadFileJsonData | UploadFileFormData)
- plugin-client/Client.tsx: adds a contentType parameter with literal
  union type and default; uses runtime ternary for form-data dispatch
- plugin-client generators: fix isFormData/hasFormData checks to scan
  all content entries via .some() instead of only content[0]
- internals/tanstack-query: extend buildMutationArgParams to accept
  optional extraBodyParams for contentType forwarding
- plugin-react-query/MutationOptions + Mutation: propagate contentType
  through mutation variables and client call when multiple content types
- plugin-react-query/mutationGenerator: make buildFormData import
  conditional on operation actually using multipart/form-data
- Single-content-type operations are unchanged (backwards-compatible)

Tests added for multiContentType case in all three generators.

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
@codesandbox
Copy link
Copy Markdown

codesandbox Bot commented Apr 26, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: a35c1b2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@kubb/plugin-client Minor
@kubb/plugin-react-query Minor
@kubb/plugin-ts Minor
@kubb/plugin-mcp Minor
@kubb/plugin-vue-query Minor
tests-3.0.x Patch
e2e Patch
performance Patch
@kubb/plugin-cypress Minor
@kubb/plugin-faker Minor
@kubb/plugin-msw Minor
@kubb/plugin-redoc Minor
@kubb/plugin-zod Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@autofix-ci
Copy link
Copy Markdown
Contributor

autofix-ci Bot commented Apr 26, 2026

Auto fix applied for linting and formating

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 26, 2026

More templates

@kubb/plugin-client

npm i https://pkg.pr.new/@kubb/plugin-client@91

@kubb/plugin-cypress

npm i https://pkg.pr.new/@kubb/plugin-cypress@91

@kubb/plugin-faker

npm i https://pkg.pr.new/@kubb/plugin-faker@91

@kubb/plugin-mcp

npm i https://pkg.pr.new/@kubb/plugin-mcp@91

@kubb/plugin-msw

npm i https://pkg.pr.new/@kubb/plugin-msw@91

@kubb/plugin-react-query

npm i https://pkg.pr.new/@kubb/plugin-react-query@91

@kubb/plugin-redoc

npm i https://pkg.pr.new/@kubb/plugin-redoc@91

@kubb/plugin-ts

npm i https://pkg.pr.new/@kubb/plugin-ts@91

@kubb/plugin-vue-query

npm i https://pkg.pr.new/@kubb/plugin-vue-query@91

@kubb/plugin-zod

npm i https://pkg.pr.new/@kubb/plugin-zod@91

commit: a35c1b2

claude added 5 commits April 26, 2026 14:05
Real Petstore operations (AddPet, UpdatePet, PlaceOrder, etc.) declare
multiple content types, so snapshots now reflect the new per-content-type
type aliases and union alias generated by plugin-ts, plus the contentType
parameter added to client functions by plugin-client.

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
Content types like 'application/json-patch+json; x-api-version=1.0'
were producing invalid TypeScript identifiers (e.g. 'Json-patch+json;
x-api-version=1.0Data') because getContentTypeSuffix did not strip
content-type parameters or sanitize non-alphanumeric characters.

Fix: strip the '; param=value' portion first, then split the subtype on
any non-alphanumeric run and join the resulting words in PascalCase.
Also add deduplication with a numeric suffix for the rare case where
multiple content types collapse to the same identifier (e.g. 'text/json'
and 'application/*+json' both resolve to 'Json').

Fixes E2E failure on the requestBody.yaml schema (GitHub issue #80).

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
Petstore operations like AddPet, UpdatePet, PlaceOrder, CreateUser, and
UpdateUser declare multiple content types (application/json, application/xml,
application/x-www-form-urlencoded), so the generated examples now include
per-content-type type aliases, union aliases, and contentType parameters.

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
…a upload example

- plugin-vue-query/Mutation.tsx: add buildContentTypeParam helper and
  thread contentType through buildMutationArgParams (for the mutation
  variables type) and clientCallParamsNode extraParams (for the actual
  client call), matching the react-query fix already in place.

- examples/*/petStore.yaml: extend the uploadFile operation with an
  application/json content type (upload-by-URL with {url: string}) in
  addition to the existing multipart/form-data ({file: binary}). This
  gives every example a real operation with two different schemas per
  content type, demonstrating the full feature end-to-end.

- Regenerated all affected examples and updated integration snapshots.

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
- Extract getContentTypeInfo and buildContentTypeParams into @internals/tanstack-query and re-export from react-query/vue-query utils
- Add getContentTypeInfo to plugin-client utils for local use
- Remove duplicated inline content-type computation from MutationOptions, vue-query/Mutation, and Client components
- Move getContentTypeSuffix and getPerContentTypeName to module level in typeGenerator
- Convert requestType and responseType IIFEs to named inner functions

https://claude.ai/code/session_011Q7vkeTEWgmr3TKnfGue4q
@stijnvanhulle stijnvanhulle marked this pull request as ready for review April 26, 2026 16:26
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 26, 2026
@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot merge main and update test snapshots

Copilot AI and others added 2 commits April 30, 2026 16:56
…type-request-kmujk

# Conflicts:
#	packages/plugin-client/src/utils.ts

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
Agent-Logs-Url: https://github.com/kubb-labs/plugins/sessions/c85ca4a2-d441-4b4d-bf3b-b7e89e6f475a

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 30, 2026

@copilot merge main and update test snapshots

Done. Merged main into the branch (resolved a conflict in packages/plugin-client/src/utils.ts) and updated the 6 outdated snapshots. All 827 tests pass. Commit: 23dda05

@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot contentType should be part of the config, update also axios and fetch clients

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 30, 2026

@copilot contentType should be part of the config, update also axios and fetch clients

Done. contentType is now part of RequestConfig instead of a standalone function parameter.

Changes:

  • RequestConfig in both axios.ts and fetch.ts templates now has contentType?: string
  • The fetch client sets the Content-Type header from config.contentType (skipped for multipart/form-data so the browser can append the boundary automatically)
  • Generated functions for multi-content-type operations now destructure contentType from config with a default: const { client: request = fetch, contentType = 'application/json', ...requestConfig } = config
  • contentType is no longer a mutation variable in react-query/vue-query — users set it via the client config option instead
  • Removed buildContentTypeParams helper (no longer needed)

Commit: de445bc

@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot resolve examples typecheck and generation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 1, 2026

@copilot resolve examples typecheck and generation

Fixed. The root cause was that src/clients/axios.ts and src/clients/fetch.ts (the published library files that examples import from @kubb/plugin-client/clients/*) didn't have contentType in RequestConfig — only the template files did.

Changes:

  • Added contentType?: string to RequestConfig in src/clients/axios.ts and src/clients/fetch.ts
  • Added contentType?: string to RequestConfig in the custom axios-client.ts used by the advanced example
  • Regenerated all examples — all 14 generate and typecheck cleanly

Commit: 3bff86a

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cloudflare.com
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/zod/node_modules/.bin/../kubb/bin/kubb.js --config kubb.config.js (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/react-query/node_modules/.bin/../kubb/bin/kubb.js generate (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/fetch/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
  • dns.google.com
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/zod/node_modules/.bin/../kubb/bin/kubb.js --config kubb.config.js (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/react-query/node_modules/.bin/../kubb/bin/kubb.js generate (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/fetch/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
  • one.one.one.one
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/zod/node_modules/.bin/../kubb/bin/kubb.js --config kubb.config.js (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/react-query/node_modules/.bin/../kubb/bin/kubb.js generate (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/fetch/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot merge main and resolve merge conflicts

Copilot AI and others added 2 commits May 4, 2026 11:22
…type-request-kmujk

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
Agent-Logs-Url: https://github.com/kubb-labs/plugins/sessions/3a26c654-ac62-448a-aa8c-78dafa9bb8d1

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 4, 2026

@copilot merge main and resolve merge conflicts

Done. Merged main (beta.4 release + extension.yaml per-package files) with no conflicts. All 827 tests pass and all 14 examples generate and typecheck cleanly. Commit: a15bb52

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cloudflare.com
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/simple-single/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.js -l info (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/generators/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/client/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
  • dns.google.com
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/simple-single/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.js -l info (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/generators/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/client/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
  • one.one.one.one
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/simple-single/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.js -l info (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/generators/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)
    • Triggering command: /opt/hostedtoolcache/node/22.22.2/x64/bin/node node /home/REDACTED/work/plugins/plugins/examples/client/node_modules/.bin/../kubb/bin/kubb.js generate --config kubb.config.ts (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot research if this appraoch make sense and add plan in what we have and what is still needed

Agent-Logs-Url: https://github.com/kubb-labs/plugins/sessions/e4abfa59-b12a-410e-a773-f902bd2bef3f

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels May 4, 2026
Copy link
Copy Markdown
Contributor

Copilot AI commented May 4, 2026

@copilot research if this appraoch make sense and add plan in what we have and what is still needed

Done in 22e0d48. I reviewed the approach with a gpt-5.5 research pass, updated docs/plans/multi-content-type-request-body.md with the current state and remaining work, and added RTK installation from rtk-ai/rtk to the Copilot setup workflow.

Agent-Logs-Url: https://github.com/kubb-labs/plugins/sessions/326bcae9-a45c-440a-a6c1-0130f8b516f8

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
@stijnvanhulle
Copy link
Copy Markdown
Contributor Author

@copilot resolve merge conflicts

…type-request-kmujk

Co-authored-by: stijnvanhulle <5904681+stijnvanhulle@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 4, 2026

@copilot resolve merge conflicts

Merged latest origin/main into the branch and pushed the merge commit a35c1b21. Validation passed: pnpm run lint, pnpm run typecheck, and affected package tests for plugin-client/react-query/vue-query/zod.

with:
node-version: '22'

- name: Install RTK
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot not needed anymore, our setup config has rtk already

@stijnvanhulle stijnvanhulle merged commit 2ff4885 into main May 4, 2026
15 of 16 checks passed
@stijnvanhulle stijnvanhulle deleted the claude/multi-content-type-request-kmujk branch May 4, 2026 13:12
Copilot stopped work on behalf of stijnvanhulle due to an error May 4, 2026 13:13
@github-actions github-actions Bot mentioned this pull request May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants