# GraphQL File Upload for Federated APIs

Clients send files alongside their GraphQL mutation. The router routes them to the right subgraph automatically.

One GraphQL endpoint. One auth layer. No encoding bloat.

## Multipart request flow

- **Client**  multipart/form-data  operations · map · files
- **Cosmo Router**  parse · validate · route
- **Subgraph**  Upload scalar

## The problem

### Uploads should not live outside your graph

Federated teams still need files: avatars, attachments, imports. The wrong defaults split your API and punish bandwidth.

#### A separate REST endpoint fragments the API

When uploads bypass GraphQL, teams often reimplement authentication, rate limiting, and logging on a second path. Errors and traces follow different shapes than the rest of the API, and clients carry an extra integration for the same product.

#### Base64 over JSON wastes bandwidth

Putting binary data inside JSON usually means base64, which grows the payload by roughly a third compared to the raw file. Parsing that JSON costs more CPU on both sides, and the extra bytes add up across large files and repeated attempts.

#### Custom upload schemes don't travel

If each team invents its own envelope, every client library, SDK, and piece of tooling has to learn each one. Standard clients (Apollo, Relay, urql) already speak the multipart spec.

## Our solution

### Accept file uploads through your GraphQL API with no second endpoint

Cosmo Router implements the GraphQL multipart request specification. Clients send a `multipart/form-data` request with three parts: `operations`, `map`, and file parts. Declare an `Upload` scalar in your schema and you can accept files from any spec-compliant client.

### The three parts

- **operations**: the GraphQL query with file variables set to `null`
- **map**: associates file fields to variable paths
- **file parts**: the actual bytes

### At request time

1. Instead of splitting uploads onto a separate REST service, callers use the same federated GraphQL URL they already use for reads and mutations. The router accepts a multipart body, interprets it according to the spec, and continues the request as normal GraphQL work.

2. The router parses the payload, validates the mapping, and attaches each file to the correct variable before federation routes the mutation.

3. Declare an `Upload` scalar in your schema and implement it in the resolver layer: any spec-compliant client can send files through the same GraphQL endpoint as everything else.

4. File size and file count limits are configured on the router in `config.yaml`; malformed multipart requests are rejected at the router.

## One endpoint. Standard tooling. Binary on the wire.

### Before & After

| Before Cosmo | With Cosmo |
| --- | --- |
| Separate REST endpoint for uploads | One GraphQL endpoint for everything |
| Base64 encoding with 33% overhead | Binary multipart transfer |
| Custom routing logic for uploads | Automatic federation routing |
| Bespoke upload envelopes per client | Standard spec any major client speaks |

## Router behavior

### Technical behavior

- The router parses multipart payloads per spec and rejects malformed ones.
- File size and file count limits are configured on the router in `config.yaml`.
- Files are forwarded to the subgraph that resolves the mutation.
- Works with Apollo Client, Relay, urql, and other spec-compliant clients out of the box.

## Requirements

- Declare `scalar Upload` in the subgraph schema and implement it in resolvers.
- Use a multipart-capable GraphQL client.

## How multipart upload works

### One POST to your GraphQL endpoint

Clients keep the same gateway URL; the body is multipart instead of JSON-only, using the shape common GraphQL clients already support.

### Malformed uploads rejected early.

The router parses the payload, reads the three parts, validates the mapping, and attaches each file to the correct variable.

### Automatic federation routing

The router forwards to the subgraph that owns the mutation.

### Binary: no base64 tax.

The subgraph stores or processes the file; the resolver writes to S3, disk, or another service and returns the mutation result.

## Schema examples

### Declare `Upload` once

Avatar, multi-file tickets, or bulk import: same scalar pattern.

### Example schema

```graphql
# GraphQL schema with file upload
scalar Upload
```

### Limits in config

Cap upload size and file count at the router. See the docs for all options.

## Upload through GraphQL today

Multipart spec, one router endpoint, configurable limits.

## FAQ

### GraphQL file upload

- What GraphQL file upload spec does Cosmo Router implement?
- Can I upload multiple files in a single GraphQL mutation?
- Which GraphQL clients support multipart file uploads?
- How do I limit GraphQL upload file size and count?
- What do I need to change in my subgraph to accept GraphQL file uploads?
- Is GraphQL multipart upload faster than base64 encoding?

More detail in the [file upload documentation](https://cosmo-docs.wundergraph.com/router/file-upload).
