Upload multipart/form-data image to Sanity: "unsupported image format" error

64 replies
Last updated: May 4, 2023
how to upload mutlipart/form-data image in sanity cdn, I'm getting input buffer contains unsupported image format error. Below is my code-
export const config = {

api: {

bodyParser: false,

},

};


export default async function handler(

req: NextApiRequest,

res: NextApiResponse

) {

const data = await new Promise((resolve, reject) => {

const form = formidable();

form.parse(req, (err, fields, files) => {

if (err) {

reject(err);

}

resolve({ req, fields, files });

});

});

const { fields, files } = data;


try {

const { _id } = await client.assets.upload("image", files);

res.status(200).json(_id);

} catch (err) {

res.status(400).send(err);

}

}
AI Update

The issue you're encountering happens because formidable returns a file object, but client.assets.upload() expects the actual file data (a stream, buffer, or File object). Here's how to fix it:

Since you're already using formidable, you need to read the actual file from the temporary path formidable creates:

import fs from 'fs';
import formidable from 'formidable';
import {client} from './sanityClient'; // your sanity client

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  const form = formidable();
  
  try {
    const [fields, files] = await form.parse(req);
    
    // formidable v3+ returns arrays
    const file = files.image?.[0] || files.file?.[0];
    
    if (!file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }
    
    // Create a read stream from the temporary file
    const fileStream = fs.createReadStream(file.filepath);
    
    const asset = await client.assets.upload('image', fileStream, {
      filename: file.originalFilename,
    });
    
    res.status(200).json({ _id: asset._id });
  } catch (err) {
    console.error('Upload error:', err);
    res.status(400).json({ error: err.message });
  }
}

Solution 2: Using raw body (simpler approach)

If you don't need formidable's features, you can send the file as a raw body:

Backend:

import {client} from './sanityClient';
import getRawBody from 'raw-body';

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  try {
    const rawBody = await getRawBody(req);
    
    const asset = await client.assets.upload('image', rawBody, {
      contentType: req.headers['content-type'] || 'image/jpeg',
      filename: req.headers['x-filename'] || 'uploaded-image.jpg',
    });
    
    res.status(200).json({ _id: asset._id });
  } catch (err) {
    console.error('Upload error:', err);
    res.status(400).json({ error: err.message });
  }
}

Frontend:

const handleSubmit = async (e) => {
  e.preventDefault();
  const file = e.target[0].files[0];
  
  const arrayBuffer = await file.arrayBuffer();
  
  const response = await fetch('/api/uploadImage', {
    method: 'POST',
    body: arrayBuffer,
    headers: {
      'Content-Type': file.type,
      'X-Filename': file.name,
    },
  });
  
  const data = await response.json();
  console.log(data);
};

Key Points:

  1. Always disable Next.js body parser with bodyParser: false
  2. With formidable: Use fs.createReadStream(file.filepath) to read the temp file
  3. File field name: Make sure your form field name matches what you're accessing in files.image or files.file
  4. Formidable version: v3+ returns arrays, so use files.image[0] instead of files.image

According to the official Sanity documentation on uploading assets, client.assets.upload() accepts streams, buffers, or File objects - but not formidable's file descriptor objects directly.

Install raw-body if using Solution 2: npm install raw-body

Show original thread
64 replies
Hey
user U
. Your code looks okay, apart from the fact it seems you're trying to upload an array of files? You can see how to do it here: https://www.sanity.io/docs/js-client#uploading-assets (on "Uploading Assets")
user Z
my code throws expected a string, buffer or readable stream, instead got an object error
user U
Since a file is just a blob, you can use file.arrayBuffer() to retrieve a buffer from it as well
You will need to
await
that one, though
user Z
Look at my frontend code ->
export default function Component() {
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formdata = new FormData(e.target);

    fetch("/api/uploadImage", {
      method: "POST",
      body: formdata,
    })
      .then((res) => res.json())
      .then((r) => console.log(r))
      .catch((err) => console.log(err));
  };

  return (
    <div>
      <form encType="multipart/form-data" onSubmit={handleSubmit}>
        <input type="file" id="inputFile" name="avatar" />
        <button
          type="submit"
          className="rounded-md bg-indigo-500 px-4 py-1 text-white"
        >
          Upload
        </button>
      </form>
    </div>
  );
}
Have you made sure you're not uploading an array?
I don't use Formidable, but if "files" is an array, you'll get an error
files is an object of type formidable.File
Ah, there we have it
So, what should I do
Let me see how I can help you since I don't know Formidable
Can you suggest me, how can I take image input from the user at the frontend, and upload the image to sanity cdn.
Sanity itself can upload from the type "File". It's Formidable's file type that's the issue. Removing Formidable would work
Yes, I can upload the image to sanity cdn, directly from the frontend, by client.assets.upload('image', event.target.files[0]). But it will expose my token to the client.
You don't need to do that on the frontend. Your backend will also get the file via POST
yeah, there's the problem. then, i have to POST the image file contents as formData. And, it'll create the same issue again
The only issue you're facing is Formidable is converting the type. If you remove Formidable, you will get the raw File type, which is accepted by the Sanity client library
In case it doesn't, you can use file.arrayBuffer() to get the contents as a buffer
Will it accept the raw multipart/form-data
Ok, let me try removing the parser
Oki doki
Not working🥲 -&gt; backend -
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const file = req.body;
  try {
    const response = await client.assets.upload("image", Buffer.from(file));
    res.status(200).json(response);
  } catch (err) {
    console.log(err); // input buffer contains unsupported image format
    res.status(500).send(err);
  }
}
frontend -&gt;

export default function Component() {
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const file = e.currentTarget[0].files[0];

    fetch("/api/uploadImage", {
      method: "POST",
      body: file,
    })
      .then((res) => res.json())
      .then((r) => console.log(r))
      .catch((err) => console.log(err));
  };

  return (
    <div>
      <form encType="multipart/form-data" onSubmit={handleSubmit}>
        <input type="file" id="inputFile" name="avatar" />
        <button
          type="submit"
          className="rounded-md bg-indigo-500 px-4 py-1 text-white"
        >
          Upload
        </button>
      </form>
    </div>
  );
}
Tried but no success🥲frontend -&gt;

export default function Component() {
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const file = e.currentTarget[0].files[0];

    fetch("/api/uploadImage", {
      method: "POST",
      body: file,
    })
      .then((res) => res.json())
      .then((r) => console.log(r))
      .catch((err) => console.log(err));
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="file" id="inputFile" name="avatar" />
        <button
          type="submit"
          className="rounded-md bg-indigo-500 px-4 py-1 text-white"
        >
          Upload
        </button>
      </form>
    </div>
  );
}
backend -&gt;

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const file = req.body;
  try {
    const response = await client.assets.upload("image", Buffer.from(file));
    res.status(200).json(response);
  } catch (err) {
    console.log(err); // input buffer contains unsupported image format
    res.status(500).send(err);
  }
}
Okay, you're in the right track. The only thing now is you're looking for
req.body.avatar
(since your field is named "avatar"). A few console.logs will go a long way in testing that too
actually
const file = e.currentTarget[0].files[0];
is itself the image file contents
Oh that's right, I didn't notice you were fetching that directly. Are you getting the same error?
if i upload the file in the client side using client.assets.upload('image', file). There are no issues. The main issue is when i try to POST the data
I would again suggest you go the alternate route of POSTing the array buffer and reading it with req.arrayBuffer() to send it to the client library
You're facing the same issue, which is the lack of a "File" type
You need either a stream or a buffer
const file = e.currentTarget[0].files[0];
=&gt;

const file = e.currentTarget[0].files[0].arrayBuffer();

The Request type also has an arrayBuffer() Promise
then await
req.arrayBuffer()
should work in the backend. Let me cook
Correct
req.arrayBuffer() is not available in the req object, i tried uploading the req.body, got the same error. I think there is some issue. Sanity studio also uploads file from the frontend, if you upload images from the Sanity Desk.
Do you have a quick repo I can use to reproduce this so I can fix it for you? I'm pretty sure we're walking around the solution without knowing
sorry I don't have a repo for it now. I have to make a new one. But the task is simple.1. Take image input from the users at frontend,
2. POST the image data to external API, and upload the image to sanity cdn
i
Okay let me use some random project I have here, give me a second
Ok
If you can solve it, please share me the logic
Naturally. Getting to it now
Okay I think I got it
user U
Add this to your handler:
export const config = {
  api: {
    bodyParser: false
  }
};
Top-level
Then client.assets.upload('image', req.read(read.readableLength))
Client-side stays the same
The issue was Next was trying to automatically parse the body and was ruining it
Now, you're able to upload the image to the sanity cdn?
Correct
Can you please share the both frontend and backend code. I want to see how you're sending the POST data
	const submit = (e: FormEvent) => {
		e.preventDefault();
		const file = (e.currentTarget as HTMLFormElement).file.files[0] as File;
		fetch('/api/hello', {
            method: 'POST',
            body: file
        })
	};

	return (
        <form onSubmit={submit}>
            <input type="file" name="file" />
            <button type="submit">Submit</button>
        </form>
	);
export const config = {
  api: {
    bodyParser: false
  }
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  const buffer = req.read(req.readableLength);
  client.assets.upload('image', buffer);
  res.status(200).json({ name: 'John Doe' })
}
Don't mind my response haha
Thanks ♥️.
And coding exists because of John Doe😂
Hahaha did it work for you now?
If it worked for you, It should work on my end also.
Sorry to say, but your code throws premature end of input file error. I tried uploading different images but got the same results.
export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const buffer = req.read(req.readableLength);
  client.assets
    .upload("image", buffer)
    .then((imageAsset) => res.status(200).json(imageAsset))
    .catch((err) => res.status(400).send(err)); // premature end of input file
}
user U
Since the request object is a Readable stream, your readableLength may be different from the total length. Check the corresponding Node documentation or use a package like read-all-stream instead
finally solved it by converting the image file to arrayBuffer in the frontend, and reading the req stream at the backend with the help of 'raw-body' module.😄
user U
I’m facing the same issue, could you share your solution with us?
I used days trying to figure this out. If anyone in the future is trying to do something similar, this is how I did it (with the help of the answers above)

// frontend
const handleUpload = async () => {
      // const selectedFile = target.files[0]
      if (!selectedFile) return;
      const formData = new FormData();
      const arrayBuffer = await selectedFile.arrayBuffer();
      formData.append("image", arrayBuffer);
      const { data } = await <http://axios.post|axios.post>("/api/image", arrayBuffer, {
        headers: {
          "Content-Type": "application/octet-stream",
        },
      });
  };

// backend
api/image.js

import { client } from "../../lib/sanityClient";
import getRawBody from "raw-body";

export const config = {
  api: {
    bodyParser: false,
  },
};

const handler = async (req, res) => {
  const rawBody = await getRawBody(req);  
  await client.assets.upload("image", rawBody, {
    contentType: "image/jpeg",
  });
  res.status(201).json({ message: "success" });
};

export default handler;
Now I can finally sleep
😴

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?