TanStack / TanStack/router

Support streaming file uploads in server functions

Open
#5,704 0 comments 16 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Start

Describe the bug

Currently, TanStack Start automatically calls await request.formData() in server functions when the content-type is multipart/form-data, which loads the entire file into memory before the handler is invoked. This prevents implementing true streaming file uploads with size limit enforcement.

Reference: server-functions-handler.js:49

if (formDataContentTypes.some(
  (type) => contentType && contentType.includes(type)
)) {
  // ...
  const formData = await request.formData();  // <-- File fully loaded into memory here
  // ...
  return await action(params, signal);  // Handler called after file is in memory
}
Current Behavior

When a user uploads a large file (e.g., 100MB, 1GB, or even 10GB):

  1. The entire file is loaded into server memory via request.formData()
  2. Only then is the server function handler called
  3. File size limits cannot be enforced before memory consumption
  4. Large uploads can crash the server or cause OOM errors
  5. True streaming from network → storage is impossible
Expected Behavior

Server functions should be able to access the raw request body stream via getRequest() before TanStack Start parses the FormData, allowing developers to:

  1. Enforce file size limits during upload (reject at 25MB instead of buffering 10GB)
  2. Stream files directly from network to cloud storage (S3, Azure Blob, etc.)
  3. Implement proper backpressure handling
  4. Minimize memory footprint for file uploads
Use Case

Streaming file uploads to Azure Blob Storage with size enforcement:

export const uploadFileRpc = createServerFn({ method: 'POST' })
  .middleware([authMiddleware])
  .handler(async (ctx) => {
    const request = getRequest();
    
    // Would like to access raw body stream here
    const busboy = new Busboy({
      headers: {
        'content-type': request.headers.get('content-type') || '',
      },
      limits: {
        fileSize: 25 * 1024 * 1024, // Enforce 25MB limit DURING upload
      },
    });

    busboy.on('file', (fieldname, fileStream) => {
      // Stream directly to Azure without buffering in memory
      const webStream = Readable.toWeb(fileStream);
      await objectStorage.upload({ stream: webStream });
    });

    // Currently doesn't work because request.body is already consumed
    Readable.fromWeb(request.body).pipe(busboy);
  });
Current Workaround

The only workaround is to bypass TanStack Start server functions entirely and create separate Hono/Express routes for file uploads, which defeats the purpose of having a unified server function API.

Impact

This affects any TanStack Start application that needs to:

  • Handle large file uploads (videos, backups, datasets)
  • Implement proper file size validation
  • Stream files to cloud storage
  • Build production file upload services
Steps to Reproduce the Bug or Issue

Try to stream the file from user to other source, for example blob storage

Expected behavior

Add a configuration option or special handler type for server functions that need raw stream access:

Option 1: Skip FormData parsing when a flag is set

export const uploadFileRpc = createServerFn({ 
  method: 'POST',
  parseBody: false, // Skip automatic FormData parsing
})
  .handler(async (ctx) => {
    const request = getRequest();
    // request.body stream is still available
  });

Option 2: Provide a separate handler for raw body access

export const uploadFileRpc = createServerFn({ method: 'POST' })
  .rawBodyHandler(async (ctx, request) => {
    // Access raw request before any parsing
    // request.body stream is available here
  });
Screenshots or Videos

No response

Platform
  • @tanstack/react-start: 1.133.20
  • Runtime: Node.js with Hono
Additional context

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/start-server-core/src/server-functions-handler.ts, especially the multipart/form-data path that calls request.formData(). Trace how server functions expose getRequest() and how the request body is consumed. Done means a documented or tested server-function path can access the raw body stream before parsing and support upload size enforcement.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
api, backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.