How to Fix 'Variable "$file" Got Invalid Value {}; Upload Value Invalid' Error When Uploading Files with GraphQLClient (graphql-request)
File uploads are a common requirement in modern web applications, and GraphQL has become a popular choice for handling API requests due to its flexibility. However, integrating file uploads with GraphQL clients like graphql-request (a lightweight, promise-based client) can sometimes lead to cryptic errors. One such error is:
Variable "$file" got invalid value {}; Upload value invalid
This error typically occurs when the GraphQL server rejects the file upload because it cannot parse the provided "file" variable. If you’ve encountered this, you’re not alone. In this blog, we’ll demystify this error, explore its root causes, and provide step-by-step solutions to fix it. By the end, you’ll be able to confidently upload files using graphql-request without hitting this roadblock.
Table of Contents#
- Understanding the Error
- Root Causes of the Error
- Step-by-Step Solutions
- Example: Working File Upload with GraphQLClient
- Troubleshooting Tips
- Conclusion
- References
Understanding the Error#
What Does the Error Mean?#
The error Variable "$file" got invalid value {}; Upload value invalid indicates that the GraphQL server received an invalid value for the $file variable in your upload mutation. The {} suggests the server expected a valid file payload (e.g., binary data, stream, or file metadata) but received an empty or malformed object instead.
This typically happens when the client fails to properly format the file data or misconfigures the request, preventing the server from parsing the file.
Common Scenarios#
You might encounter this error in:
- Browser environments when uploading files via a form input.
- Node.js environments when uploading files from a server (e.g., processing user uploads or migrating data).
- Any scenario where
graphql-requestis used to send a mutation with a$filevariable, but the file data is not correctly passed or formatted.
Root Causes of the Error#
To fix the error, we first need to identify its root cause. Here are the most common culprits:
1. Missing or Incorrect Upload Scalar in Schema#
GraphQL does not natively support file uploads. To handle uploads, servers use a custom scalar type (typically named Upload), defined using libraries like graphql-upload. If your server’s schema does not explicitly define the Upload scalar, or if the mutation argument uses an incorrect type (e.g., String instead of Upload), the server will reject the file variable as invalid.
2. GraphQLClient Misconfiguration (Content-Type Issue)#
By default, graphql-request sends requests with the Content-Type: application/json header. However, file uploads require the multipart/form-data content type, which packages the query, variables, and file data into a single request. If graphql-request is not configured to use multipart/form-data, the server cannot parse the file, leading to the "invalid value" error.
3. Invalid File Variable Formatting#
The $file variable must be a file-like object (e.g., a browser File/Blob, or a Node.js ReadStream/Buffer). If you pass a plain JavaScript object (e.g., { filename: "image.jpg" }), the server will reject it because it expects binary data or a stream, not metadata.
4. Server-Side Upload Handling Issues#
Even if the client is configured correctly, the error may stem from server-side misconfiguration. For example:
- The server is not using middleware to parse multipart requests (e.g.,
graphql-upload’sprocessRequest). - The resolver for the upload mutation is not properly handling the
Uploadscalar (e.g., expecting a file path instead of a stream).
Step-by-Step Solutions#
Let’s address each root cause with actionable solutions.
1. Verify the GraphQL Schema Defines the Upload Scalar#
First, ensure your server’s schema explicitly defines the Upload scalar and uses it in the mutation argument.
Example Server Schema (using graphql-upload):
# Import the Upload scalar from graphql-upload
scalar Upload
type Mutation {
uploadFile(file: Upload!): File! # Use Upload scalar for the file argument
}
type File {
id: ID!
filename: String!
mimetype: String!
url: String!
}If the Upload scalar is missing or the mutation uses an incorrect type (e.g., String), update the schema and restart the server.
2. Configure GraphQLClient to Send Multipart Requests#
graphql-request does not automatically handle multipart/form-data requests. To fix this, you need to:
- Construct a
FormDataobject (browser) or use theform-datapackage (Node.js) to package the query, variables, and file. - Set the
Content-Typeheader tomultipart/form-datawith a boundary (automatically generated byFormData).
Browser Environment#
In browsers, use the native FormData API to build the request payload. graphql-request will automatically detect FormData and set the correct headers if you pass it as the body option.
Example Configuration:
import { GraphQLClient } from 'graphql-request';
const client = new GraphQLClient('https://your-graphql-server.com/graphql');
// Define your upload mutation
const UPLOAD_MUTATION = `
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
id
filename
url
}
}
`;
// Get the file from a form input (e.g., <input type="file" id="fileInput" />)
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0]; // Browser File object
// Create variables with the file
const variables = { file };
// Send the request with FormData
async function uploadFile() {
try {
const data = await client.request(UPLOAD_MUTATION, variables);
console.log('Upload successful:', data);
} catch (error) {
console.error('Upload failed:', error);
}
}Note: Modern versions of graphql-request (v4+) automatically detect file variables and switch to multipart/form-data. If you’re using an older version, you may need to manually construct FormData (see Section 4).
Node.js Environment#
In Node.js, use the form-data package to create a multipart request. Install it first:
npm install form-datagraphql-request can be configured to use this FormData instance via the fetch option (using a custom fetch implementation like node-fetch).
3. Ensure Correct File Variable Formatting#
The $file variable must be a file-like object. Here’s how to format it in different environments:
Browser: Using File Objects#
In browsers, the File object (from <input type="file">) is natively supported. Ensure you pass the File directly in variables:
// Correct: Pass the File object from the input
const file = fileInput.files[0]; // typeof file === 'object' (File instance)
const variables = { file }; // ✅ Valid
// Incorrect: Passing a plain object (causes the error)
const variables = { file: { name: "image.jpg" } }; // ❌ InvalidNode.js: Using File-Like Objects#
Node.js lacks the browser’s File object, so you need to create a file-like object using ReadStream (from fs) or Buffer. Use graphql-upload’s FileUpload type or a simple object with createReadStream:
Example with fs and form-data:
import fs from 'fs';
import { createReadStream } from 'fs';
import FormData from 'form-data';
import { GraphQLClient } from 'graphql-request';
import fetch from 'node-fetch';
const client = new GraphQLClient('https://your-graphql-server.com/graphql', {
fetch: (url, options) => fetch(url, { ...options, agent: httpsAgent }), // For HTTPS (if needed)
});
// Path to the local file
const filePath = './local-file.jpg';
// Create a ReadStream for the file
const fileStream = createReadStream(filePath);
// File-like object (mimics browser File)
const file = {
createReadStream: () => fileStream,
filename: 'uploaded-file.jpg', // Required: filename for the server
mimetype: 'image/jpeg', // Optional: helps server validate type
};
const variables = { file }; // ✅ Valid file variable4. Construct the Multipart Request Manually (If Needed)#
If graphql-request isn’t auto-detecting the file variable (e.g., in older versions), manually construct the multipart/form-data request:
Browser Example with FormData#
async function uploadFileManually() {
const formData = new FormData();
// Append query and variables to FormData
formData.append('query', UPLOAD_MUTATION);
formData.append('variables', JSON.stringify({ file })); // file is a File object
// Send request with fetch (bypassing graphql-request's default JSON handling)
const response = await fetch('https://your-graphql-server.com/graphql', {
method: 'POST',
body: formData, // Automatically sets Content-Type: multipart/form-data
});
const data = await response.json();
console.log('Manual upload response:', data);
}Node.js Example with form-data#
import FormData from 'form-data';
import { createReadStream } from 'fs';
async function uploadFileManually() {
const formData = new FormData();
// Append query, variables, and file stream
formData.append('query', UPLOAD_MUTATION);
formData.append('variables', JSON.stringify({ file: null })); // Placeholder for file
formData.append('file', createReadStream('./local-file.jpg'), {
filename: 'uploaded-file.jpg',
contentType: 'image/jpeg',
});
// Send with node-fetch
const response = await fetch('https://your-graphql-server.com/graphql', {
method: 'POST',
body: formData,
headers: formData.getHeaders(), // Includes Content-Type with boundary
});
const data = await response.json();
console.log('Manual upload response:', data);
}5. Validate Server-Side Upload Setup#
If the client is configured correctly but the error persists, check the server:
-
Use
graphql-uploadMiddleware: For Express servers, usegraphql-upload’sprocessRequestto parse multipart requests:import { processRequest } from 'graphql-upload'; import express from 'express'; import { graphqlHTTP } from 'express-graphql'; import schema from './schema'; const app = express(); // Handle multipart requests before graphqlHTTP app.use(async (req, res, next) => { if (req.url === '/graphql' && req.method === 'POST') { try { req.body = await processRequest(req, res); next(); } catch (error) { res.status(400).send(error.message); } } else { next(); } }); app.use('/graphql', graphqlHTTP({ schema, graphiql: true })); -
Resolver Handling: Ensure the resolver expects an
Uploadscalar and processes it (e.g., saving the stream to disk):const resolvers = { Mutation: { uploadFile: async (_, { file }) => { const { createReadStream, filename, mimetype } = await file; // Save the stream to disk or cloud storage (e.g., S3) return { id: '1', filename, mimetype, url: 'https://example.com/file.jpg' }; }, }, };
Example: Working File Upload with GraphQLClient#
Let’s put it all together with complete examples for browsers and Node.js.
Browser Implementation#
<!-- HTML: File input -->
<input type="file" id="fileInput" accept="image/*" />
<button onclick="uploadFile()">Upload</button>
<script type="module">
import { GraphQLClient } from 'graphql-request';
const client = new GraphQLClient('https://your-graphql-server.com/graphql');
const UPLOAD_MUTATION = `
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
id
filename
url
}
}
`;
async function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('Select a file first!');
return;
}
try {
const data = await client.request(UPLOAD_MUTATION, { file });
console.log('Uploaded file:', data.uploadFile.url);
alert('Upload successful!');
} catch (error) {
console.error('Error:', error);
alert('Upload failed. Check console for details.');
}
}
</script>Node.js Implementation#
// upload.js
import { GraphQLClient } from 'graphql-request';
import { createReadStream } from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';
const client = new GraphQLClient('https://your-graphql-server.com/graphql', {
fetch,
});
const UPLOAD_MUTATION = `
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
id
filename
url
}
}
`;
async function uploadFileFromNode() {
// Read local file as stream
const fileStream = createReadStream('./local-image.jpg');
// File-like object for Node.js
const file = {
createReadStream: () => fileStream,
filename: 'node-upload.jpg',
mimetype: 'image/jpeg',
};
try {
const data = await client.request(UPLOAD_MUTATION, { file });
console.log('Uploaded file URL:', data.uploadFile.url);
} catch (error) {
console.error('Node upload error:', error);
}
}
uploadFileFromNode();Troubleshooting Tips#
If you still encounter the error, try these checks:
-
Inspect the Request Payload:
- In browsers: Open DevTools → Network tab → Select the GraphQL request → Check "Request Payload" to ensure
multipart/form-datais used and the file is included. - In Node.js: Log the
FormDatainstance or useformData.getBuffer()to verify the file is attached.
- In browsers: Open DevTools → Network tab → Select the GraphQL request → Check "Request Payload" to ensure
-
Validate the File Variable Type:
Useconsole.log(file)to confirm it’s aFile(browser) or a stream-like object (Node.js), not a plain object. -
Check Server Logs:
Look for server errors like "Invalid upload value" or "Could not parse multipart request" to identify server-side parsing issues. -
Update Dependencies:
Ensuregraphql-request,graphql-upload, andform-dataare up to date (old versions may have bugs).
Conclusion#
The "Variable '$file' Got Invalid Value {}" error is typically caused by misconfigured client requests, invalid file formatting, or server-side upload handling issues. By ensuring the Upload scalar is defined, using multipart/form-data, passing valid file-like objects, and verifying server setup, you can resolve this error and enable seamless file uploads with graphql-request.
Remember: Always validate the request content type, check the file variable type, and confirm server-side middleware is correctly parsing uploads. With these steps, you’ll be uploading files with GraphQLClient in no time!