cloudflare / cloudflare/cloudflare-typescript

v7 dropped the JSON multipart mode, breaking Workers script uploads (regresses #2648)

Open
#2,799 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
814
Forks
190
Avg merge
1h 38m
Merged PRs (30d)
2

Description

### Confirm this is a TypeScript library issue and not an underlying Cloudflare API issue

- [x] This is an issue with the TypeScript library

### Describe the bug

I bumped from 6.3.0 to 7.1.0 and my Worker deploys started failing:

```
400 {"result":null,"success":false,"errors":[{"code":10021,"message":"Uncaught SyntaxError:
Invalid left-hand side expression in prefix operation\n at worker.js:1:4"}],"messages":[]}
```

The call itself is unchanged and still matches the documented params (`metadata` object, `files` array). I dug into it and there are actually two separate regressions here, both introduced by the 7.0.0 "builtin Web fetch API, zero runtime dependencies" rewrite.

**1. The JSON multipart mode was dropped, but the call sites still assume it**

#2671 added an internal `__multipartSyntax: 'json'` flag to fix #2648. With it set, `addFormValueJson` serialized a nested object into a single JSON `File` part, and appended arrays of uploadables under the bare key. Nine resource files opted in. It's present in `src/uploads.ts` in 6.3.0 and still there in v6.5.0.

In 7.x, `src/internal/uploads.ts` has neither `__multipartSyntax` nor `addFormValueJson`. But those nine call sites still pass their params in the shape only that mode understood, so the generic encoder bracket-flattens them:

| | 6.3.0 | 7.1.0 |
|---|---|---|
| `workers.scripts.update` | `metadata→metadata` (`application/json`), `files→worker.js` | `metadata[main_module]`, `metadata[compatibility_date]`, `files[]` |
| `workers.scripts.scriptAndVersionSettings.edit` | `settings→settings` | `settings[logpush]`, `settings[compatibility_date]` |
| `snippets.update` | `metadata→metadata`, `files→snippet.js` | `metadata[main_module]`, `files[]` |

Note that the second row is #2648's own endpoint, so that issue is effectively reopened in v7.

**2. `workers.scripts.update` no longer sends a multipart boundary**

`scripts.update` hardcodes `Content-Type: application/javascript` into the request options. That was always there, but in 6.3.0 `getMultipartRequestOptions` merged `...opts.headers` and then `...encoder.headers`, so form-data-encoder's `multipart/form-data; boundary=…` always clobbered it.

v7 uses native `FormData` and native `fetch`, which only sets the boundary if you *don't* set `Content-Type` yourself. Nothing overrides the hardcoded value anymore, so the request goes out with no boundary at all. The API can't parse it as multipart and treats the whole encoded body as the script, which is why the error is a *syntax* error rather than a missing-part error: the leading `------formdata-undici-…` parses as chained `--` prefix decrements, at `1:4`.

This one masks the first. If you only fix the header, you get #2648's "Missing metadata part in multipart upload" back.

**Affected methods**

The nine that opted into the JSON mode in 6.x:

- `workers.scripts.update` (also hits problem 2)
- `workers.scripts.content.update`
- `workers.scripts.versions.create`
- `workers.scripts.scriptAndVersionSettings.edit`
- `snippets.update`
- `kv.namespaces.values.update`
- `workersForPlatforms.dispatch.namespaces.scripts.update`
- `workersForPlatforms.dispatch.namespaces.scripts.content.update`
- `workersForPlatforms.dispatch.namespaces.scripts.settings.edit`

I only confirmed `workers.scripts.update` end to end against the real API. For `scriptAndVersionSettings.edit`, `content.update` and `snippets.update` I compared the encoded request bodies between 6.3.0 and 7.1.0 (the table above). I didn't test the remaining five, but they go through the same deleted code path.

### To Reproduce

1. On 7.1.0, upload any module Worker with `workers.scripts.update`, passing `metadata` as an object and `files` as an array of `toFile(...)`, exactly as the params are typed.
2. The request goes out as `FormData` with `Content-Type: application/javascript` and no boundary.
3. The API returns `400` / code `10021`, `Invalid left-hand side expression in prefix operation at worker.js:1:4`.

The same code against 6.3.0 succeeds.

### Code snippets

```ts
import Cloudflare, { toFile } from 'cloudflare';

const client = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN });

// works on 6.3.0, fails on 7.0.0 and 7.1.0
await client.workers.scripts.update('my-worker', {
account_id: process.env.CLOUDFLARE_ACCOUNT_ID!,
metadata: { main_module: 'worker.js', compatibility_date: '2024-01-01', bindings: [] },
files: [
await toFile(Buffer.from("export default { async fetch() { return new Response('ok') } }"), 'worker.js', {
type: 'application/javascript+module',
}),
],
});
```

For anyone else stuck on this, you can force the old wire format out of the current encoder by passing `metadata` as a JSON `File`, passing a single `File` rather than an array, and deleting the hardcoded header. This deploys successfully on 7.1.0, but it contradicts the declared param types in two places, so I ended up building the multipart body by hand instead:

```ts
await client.workers.scripts.update(
'my-worker',
{
account_id: accountId,
metadata: await toFile(Buffer.from(JSON.stringify(metadata)), 'metadata', { type: 'application/json' }),
files: await toFile(Buffer.from(source), 'worker.js', { type: 'application/javascript+module' }),
} as any,
{ headers: { 'Content-Type': null } },
);
```

Ideally the fix restores the JSON multipart behavior for those nine endpoints, and drops or overrides the hardcoded `Content-Type` on `scripts.update` so `fetch` can set the boundary.

### OS

macOS 26.5.2

### Runtime version

Node v24.18.0 (plain JS, no TypeScript)

### Library version

v7.1.0 (also reproduces on v7.0.0)

Contributor guide

Open the contributing guide

Research direction

Start in src/internal/uploads.ts and compare it with the 6.x behavior described for src/uploads.ts, then trace the nine affected resource call sites. Verify the encoded multipart requests for the listed Workers, snippets, and KV methods, including the boundary handling in workers.scripts.update. Done means the JSON multipart behavior and fetch-managed boundary are restored without requiring callers to change their declared parameter types.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.