graphql-upload

raw JSON →
17.0.0 verified Sat Apr 25 auth: no javascript

Middleware and a GraphQL Upload scalar for handling file uploads via multipart requests in Node.js GraphQL servers (Express, Koa, Apollo). Version 17.0.0 is ESM-only, requires Node.js ^18.18.0 || ^20.9.0 || >=22.0.0, and graphql ^16.3.0. Key differentiators: implements the GraphQL multipart request spec, supports deduplication of files across variables, streams files to cloud storage or filesystem, and provides both middleware functions and a low-level processRequest function for custom integrations. Released actively with breaking changes in major versions.

error Cannot find module 'graphql-upload'
cause Using import from the package root (e.g., import { GraphQLUpload } from 'graphql-upload') which is not exported since v14.
fix
Use deep imports: import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'
error TypeError: graphqlUploadExpress is not a function
cause Using named import instead of default import for the middleware module.
fix
Use default import: import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'
error Error: Cannot find module 'graphql-upload/GraphQLUpload.mjs'
cause The package version is <14.0.0 where modules were in .js files.
fix
Update to a newer version (>=14) or use the correct extension: 'graphql-upload/GraphQLUpload.js' for older versions.
error TypeError: file.createReadStream is not a function
cause Calling createReadStream on the file promise before awaiting it, or the promise failed.
fix
Await the file promise first: const { createReadStream } = await file;
breaking Since v16, the package is ESM-only in .mjs files and no longer supports CommonJS require(). Deep imports must be used.
fix Switch to import syntax and use deep imports like import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'.
breaking In v14, the main index module was removed. Imports from 'graphql-upload' no longer work.
fix Use deep imports for each module (e.g., 'graphql-upload/GraphQLUpload.mjs').
deprecated GraphQLUpload type import path changed. Previously import type { GraphQLUpload } from 'graphql-upload/Upload.mjs' now must be from 'graphql-upload/GraphQLUpload.mjs'.
fix Update type imports: import type { GraphQLUpload } from 'graphql-upload/GraphQLUpload.mjs'.
gotcha File upload promise must be awaited in resolvers, otherwise the server may send a response before the upload completes, causing client disconnection.
fix Ensure to await the file promise and handle stream errors: const { createReadStream } = await file;
security The busboy dependency used for multipart parsing had a vulnerability (CVE-2022-24434) affecting versions <15.0.0 of graphql-upload.
fix Upgrade to graphql-upload >=15.0.0 which uses busboy v1 with the fix.
breaking In v15, busboy updated to v1, which changed some error messages. Code relying on exact error strings may break.
fix Update any error handling to match the new busboy error messages.
gotcha The process must have read and write access to os.tmpdir() and sufficient disk space for concurrent uploads.
fix Ensure os.tmpdir() is writable and monitor disk space.
npm install graphql-upload
yarn add graphql-upload
pnpm add graphql-upload

Express server with graphql-upload middleware handling a single file upload mutation using the Upload scalar.

import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
import { buildSchema } from 'graphql';
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';

const schema = buildSchema(`
  scalar Upload

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

  type Mutation {
    singleUpload(file: Upload!): File!
  }

  type Query {
    _: Boolean
  }
`);

const rootValue = {
  singleUpload: async (parent, { file }) => {
    const { filename, mimetype, encoding, createReadStream } = await file;
    const stream = createReadStream();
    // Process stream (e.g., save to disk or cloud)
    // For demo, just discard the stream
    stream.resume();
    return { filename, mimetype, encoding };
  }
};

const app = express();
app.use('/graphql', graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
app.all('/graphql', createHandler({ schema, rootValue }));
app.listen(4000, () => console.log('Server running on port 4000'));