Skip to content

feat(api-client): multipart content handling#2522

Draft
Maciek Kucmus (mkucmus) wants to merge 7 commits into
mainfrom
feat/api-client-multipart-content
Draft

feat(api-client): multipart content handling#2522
Maciek Kucmus (mkucmus) wants to merge 7 commits into
mainfrom
feat/api-client-multipart-content

Conversation

@mkucmus

@mkucmus Maciek Kucmus (mkucmus) commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

1. Fix multipart/form-data and binary uploads in api-client

2. Revert the typecheck command in vue-starter-template and vue-starter-template-extended back to Nuxt's official typecheck:

Closes #1799

Problem

The client seeds a default Content-Type: application/json on every request. The runtime (browser fetch / undici) only sets the correct content type for a non-JSON body when no Content-Type is present, so that default leaked onto uploads and broke them. The previous safeguard only stripped a manually-set multipart/form-data, and only in the browser - leaving server-side uploads (the Admin client's main environment) broken.

What changed

  • The client now drops the default Content-Type whenever the body is one the runtime must type itself: FormData, Blob/File, URLSearchParams, ArrayBuffer/typed arrays, and streams.
  • Works on both the Store and Admin clients, in the browser and on the server.
  • A Content-Type you set explicitly is preserved (e.g. an image/png for a typeless Blob) - except a boundary-less multipart/form-data, which is always replaced.
  • Logic extracted into a shared resolveRequestHeaders helper used by both clients.
  • README: new "Uploading files and other binary bodies" section documenting the behavior.

Usage

Just pass the body and leave Content-Type alone:

const formData = new FormData();
formData.append("file", file);

await adminApiClient.invoke("uploadV2 post /_action/media/upload", {
  contentType: "multipart/form-data", // type metadata, ignored at runtime
  accept: "application/json",
  body: formData as unknown as { file: Blob },
});

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontends-starter-template-extended Ready Ready Preview, Comment Jun 30, 2026 10:30am

Request Review

@mkucmus Maciek Kucmus (mkucmus) changed the title Feat/api client multipart content feat(api-client): multipart content handling Jun 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes multipart/form-data and other non-JSON request bodies in @shopware/api-client by ensuring the default Content-Type: application/json does not leak onto bodies where the runtime (browser fetch / undici) must derive Content-Type itself. This aligns the client behavior with proper upload semantics for both Store and Admin clients.

Changes:

  • Added a shared resolveRequestHeaders helper to merge defaults + caller headers and drop runtime-managed Content-Type when appropriate.
  • Wired resolveRequestHeaders into both createAPIClient (Store) and createAdminAPIClient (Admin), and updated relevant tests.
  • Documented upload behavior in the api-client README and added a changeset for a patch release.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/api-client/src/resolveRequestHeaders.ts New shared header resolution logic to avoid incorrect default Content-Type on runtime-managed bodies.
packages/api-client/src/resolveRequestHeaders.test.ts Unit tests for the new header resolution helper.
packages/api-client/src/createAPIClient.ts Store client now uses shared header resolution instead of browser-only multipart stripping.
packages/api-client/src/createApiClient.test.ts Updated multipart upload test expectations; made abort assertion runtime-stable.
packages/api-client/src/createAdminAPIClient.ts Admin client now uses shared header resolution (fixes server-side uploads).
packages/api-client/src/createAdminApiClient.test.ts Added coverage to ensure FormData uploads get runtime-generated multipart boundaries.
packages/api-client/README.md Added documentation section for uploads and binary bodies.
.changeset/solid-flies-agree.md Patch changeset for the upload/binary header handling fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/api-client/src/resolveRequestHeaders.ts Outdated
Comment thread packages/api-client/src/resolveRequestHeaders.test.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.


How the client handles this for you:

- The client seeds a default `Content-Type: application/json` on every request. When the `body` is one the runtime must type itself - `FormData`, `Blob`/`File`, `URLSearchParams`, `ArrayBuffer`/typed arrays, or a stream - the client removes that default `Content-Type` so the runtime can set the correct one (a `multipart/form-data` boundary, the blob's MIME type, `application/x-www-form-urlencoded`, and so on). This applies to both the Store and Admin clients, in the browser and on the server.
Comment on lines +450 to 452
await expect(request).rejects.toThrow(
new RegExp(`\\[GET\\] "${baseURL}context": <no response>.*aborted`, "i"),
);
@patzick

Copy link
Copy Markdown
Contributor

Improvement notes from review:

  • resolveRequestHeaders currently treats only per-request headers as explicit. A client-level default such as client.defaultHeaders.apply({ "Content-Type": "application/octet-stream" }) can still be dropped for ArrayBuffer/stream bodies because callerContentType only checks callerHeaders. Either document that only per-request headers are preserved, or treat non-JSON client defaults as explicit.
  • Multipart boundary detection accepts any boundary= substring. A stricter check would avoid preserving invalid values such as multipart/form-data; boundary=. Worth adding a small test for empty/malformed boundary values.
  • The Admin client token refresh request still sends defaultHeaders directly and bypasses the new resolver. Normal /oauth/token bodies are JSON-like, so this is probably fine, but it is now an exception to the centralized request-header path and could use either a comment or a test to make that intentional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add information about multipart/form-data in api-client

3 participants