Skip to content

feat(1.x): send files anywhere in server function arguments - #2360

Open
lxsmnsyc wants to merge 1 commit into
1.xfrom
feat/server-function-file-uploads-1x
Open

lxsmnsyc wants to merge 1 commit into
1.xfrom
feat/server-function-file-uploads-1x

Conversation

@lxsmnsyc

Copy link
Copy Markdown
Member

The 1.x version of #2359.

Problem

On 1.x, a file only reaches a server function inside a FormData that is the only argument. Anything else goes through seroval, which has no plugin for Blob or File, so these calls fail with SerovalUnsupportedTypeError:

await upload(file);                    // a File as the only argument
await upload(file, { tags: ["a"] });
await upload({ file });
await upload(formData, 1);

Change

This is the same design as #2359:

  • Client: when the arguments hold files, server-runtime.ts sends multipart form data, with an x-serialized-form header.
    • The serialized arguments go in the args field, and each file goes in its own file<i> part.
    • A seroval plugin created for the call replaces each file with its part's index, wherever seroval finds it: arguments, arrays, objects, Map, Set, FormData entries.
  • Server: server-handler.ts reads that form with readUploadArgs. A plugin created for that request turns each index back into its part, and rejects a missing part, a bad index, or a missing args field.
  • Other bodies are read without that plugin, so an index in a plain body fails to deserialize.
  • Calls without files are sent as before, with x-serialized: true.

serializeToJSONString and deserializeFromJSONString take per-call plugins, tried before the built-in ones.

uploads.ts carries the same note as #2359 comparing this with seroval's binary mode.

Limits

These are the same as #2359:

  • The body is read with request.formData(), with no total size limit.
  • Files arrive as File values.
  • GET server functions can't carry files.

Verification

  • 8 new tests in runtime/uploads.test.ts go through the real handler (toWebHandler) with the client's request. They cover:
    • a file as the only argument;
    • files next to and inside other arguments, including a Map and a FormData;
    • a file used twice;
    • a cross-realm blob;
    • a call without files staying as text;
    • the three rejections.
  • New server-function-upload route plus a Cypress test.
  • packages/start: 129 tests, typecheck and build pass.
  • apps/tests: unit (2) passes. Cypress: 20 of 21 pass, including the new test. The one failure, server-function.cy.ts › "should reject the promise when the response is a 5xx without an X-Error header", fails the same way on clean 1.x.

🤖 Generated with Claude Code

A file only reached a server function inside a `FormData` that was the only
argument. Anywhere else, including as the only argument, the call failed
with `SerovalUnsupportedTypeError`, because the serializer has no plugin for
files.

When the arguments hold files, the client now sends multipart form data:
the serialized arguments in one field, and each file in its own part, with
the `x-serialized-form` header. A seroval plugin for the call replaces each
file with its part's index, wherever seroval finds it. On the server, a
plugin created for that request turns the indexes back into the parts, and
rejects a missing part or a bad index. Other bodies are read without it, so
an index means nothing there. Calls without files are sent as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 45cb05c

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

This PR includes changesets to release 1 package
Name Type
@solidjs/start 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

@pkg-pr-new

pkg-pr-new Bot commented Oct 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/start@2360

commit: 45cb05c

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.

1 participant