Skip to content

GraphQL File Upload — Complete Guide with Apollo Server and Multipart Requests

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Graphql File Upload. We cover key concepts, practical examples, and best practices to help you master this topic.

GraphQL file uploads use the Upload scalar type and multipart form data to send files alongside GraphQL mutations, enabling image uploads, CSV imports, and document attachments in a single request.

What You'll Learn

  • The Upload scalar and multipart request format
  • Implementing file upload mutations
  • File validation (size, type, dimensions)
  • Streaming files to cloud storage
  • Handling multiple file uploads

Why It Matters

Many applications need to upload files — profile pictures, threat reports, log files. GraphQL's Upload scalar lets you include file data in the same request as structured mutation arguments, keeping the API consistent. DodaTech's Durga Antivirus Pro uses file upload for submitting malware samples and scanning suspicious documents.

Real-World Use

A security analyst uploads a suspicious .exe file to Durga Antivirus Pro. The upload mutation sends the file along with metadata (threat name, source device, severity). The server validates the file size and type, streams it to S3, and returns the analysis result in the same response.

sequenceDiagram
    participant Client
    participant ApolloServer
    participant Validator
    participant S3
    Client->>ApolloServer: POST /graphql (multipart)
    Note over Client,ApolloServer: operations: { query, variables }
map: { "0": ["variables.file"] } ApolloServer->>Validator: CreateReadStream() Validator-->>ApolloServer: file size, type OK ApolloServer->>S3: pipe stream S3-->>ApolloServer: { url, key } ApolloServer-->>Client: { data: { uploadFile: { url, filename } } }

Code Examples

Example 1: Schema with Upload

scalar Upload

type File {
  filename: String!
  mimetype: String!
  encoding: String!
  url: String!
}

type Mutation {
  uploadFile(file: Upload!): File!
  uploadThreatReport(
    file: Upload!
    threatName: String!
    severity: Severity!
  ): ThreatReport!
}

Example 2: Apollo Server Upload Resolver

const { ApolloServer, gql } = require('apollo-server');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { v4: uuidv4 } = require('uuid');

const s3 = new S3Client({ region: 'us-east-1' });

const resolvers = {
  Mutation: {
    uploadFile: async (_, { file }) => {
      const { createReadStream, filename, mimetype, encoding } = await file;
      
      const stream = createReadStream();
      const key = `${uuidv4()}-${filename}`;
      
      await s3.send(new PutObjectCommand({
        Bucket: 'dodatech-uploads',
        Key: key,
        Body: stream,
        ContentType: mimetype,
      }));
      
      return {
        filename,
        mimetype,
        encoding,
        url: `https://uploads.dodatech.com/${key}`,
      };
    },
  },
};

Example 3: File Validation and Error Handling

const MAX_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'application/pdf'];

async function handleUpload(file) {
  const { createReadStream, filename, mimetype, encoding } = await file;
  
  if (!ALLOWED_TYPES.includes(mimetype)) {
    throw new UserInputError('File type not supported');
  }
  
  let totalBytes = 0;
  const chunks = [];
  const validatedStream = new require('stream').Transform({
    transform(chunk, encoding, callback) {
      totalBytes += chunk.length;
      if (totalBytes > MAX_SIZE) {
        callback(new Error('File exceeds maximum size'));
        return;
      }
      chunks.push(chunk);
      callback(null, chunk);
    },
  });
  
  createReadStream().pipe(validatedStream);
  
  return new Promise((resolve, reject) => {
    validatedStream.on('finish', async () => {
      const buffer = Buffer.concat(chunks);
      // Upload buffer to storage
      resolve({ url: 'https://...', filename, mimetype });
    });
    validatedStream.on('error', reject);
  });
}

Common Mistakes

  1. Not handling file size limits — a client could upload a multi-gigabyte file. Validate size in the stream before buffering.
  2. Buffering entire files in memory — use streams to pipe directly to storage. Buffering large files consumes excessive memory.
  3. Ignoring file type validation — clients can rename executables to .png. Validate MIME type from the actual file content, not just the extension.
  4. Not cleaning up failed uploads — if validation fails mid-stream, cancel the upload and remove any partial data from storage.
  5. Assuming all clients support multipart requests — some tools and libraries don't support the multipart format. Provide a fallback upload endpoint.

Practice Questions

  1. How does the Upload scalar work in GraphQL?
  2. Why should you use streams instead of buffering for file uploads?
  3. What is the purpose of the map field in multipart GraphQL requests?
  4. How do you validate file type and size in an upload resolver?
  5. Can you upload multiple files in a single mutation?

Challenge: Build a file upload mutation for Durga Antivirus Pro that accepts up to 5 files simultaneously, validates each for size and type, scans each file with a mock virus scanner, and returns upload results with scan status per file.

Mini Project

Create a complete file upload service with a GraphQL API that supports single and batch uploads, file type/size validation, streaming to S3, thumbnail generation for images, and virus scanning integration. Include cleanup logic for failed uploads.

FAQ

How does the client send a file in a GraphQL request?

The client sends a multipart POST request with operations (query + variables) and a map that binds Upload variables to specific file parts in the form data.

Can I use Upload scalar with subscriptions?

No. The Upload scalar only works with mutations. Upload files via a mutation and then subscribe to processing status updates.

What file size limits should I set?

Start with 5-10 MB for standard uploads. For larger files, use direct-to-S3 uploads with signed URLs and include the URL as a string in the mutation.

Does Apollo Server support file uploads out of the box?

Yes, Apollo Server includes built-in Upload scalar support via the graphql-upload package. You just need to add Upload to your scalar map.

How do I test file uploads in GraphQL Playground?

In GraphQL Playground, use the variables panel to set a file variable and click the 'Upload' button next to the variable value to select a file.

What's Next

Learn about caching strategies for GraphQL APIs

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro