Discussion on how to upload multipart/form-data images to Sanity CDN, including issues with Formidable and solutions involving arrayBuffer and raw-body module.

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);

}

}
Mar 18, 2023, 9:11 PM
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")
Mar 19, 2023, 6:56 AM
user Z
my code throws expected a string, buffer or readable stream, instead got an object error
Mar 19, 2023, 7:02 AM
user U
Since a file is just a blob, you can use file.arrayBuffer() to retrieve a buffer from it as well
Mar 19, 2023, 7:09 AM
You will need to
await
that one, though
Mar 19, 2023, 7:10 AM
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>
  );
}
Mar 19, 2023, 7:10 AM
Have you made sure you're not uploading an array?
Mar 19, 2023, 7:12 AM
I don't use Formidable, but if "files" is an array, you'll get an error
Mar 19, 2023, 7:12 AM
files is an object of type formidable.File
Mar 19, 2023, 7:13 AM
Ah, there we have it
Mar 19, 2023, 7:13 AM
So, what should I do
Mar 19, 2023, 7:13 AM
Let me see how I can help you since I don't know Formidable
Mar 19, 2023, 7:14 AM
Can you suggest me, how can I take image input from the user at the frontend, and upload the image to sanity cdn.
Mar 19, 2023, 7:15 AM
Sanity itself can upload from the type "File". It's Formidable's file type that's the issue. Removing Formidable would work
Mar 19, 2023, 7:17 AM
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.
Mar 19, 2023, 7:20 AM
You don't need to do that on the frontend. Your backend will also get the file via POST
Mar 19, 2023, 7:21 AM
yeah, there's the problem. then, i have to POST the image file contents as formData. And, it'll create the same issue again
Mar 19, 2023, 7:22 AM
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
Mar 19, 2023, 7:23 AM
In case it doesn't, you can use file.arrayBuffer() to get the contents as a buffer
Mar 19, 2023, 7:23 AM
Will it accept the raw multipart/form-data
Mar 19, 2023, 7:23 AM
Ok, let me try removing the parser
Mar 19, 2023, 7:24 AM
Oki doki
Mar 19, 2023, 7:25 AM
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>
  );
}
Mar 19, 2023, 7:45 AM
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);
  }
}
Mar 19, 2023, 7:48 AM
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
Mar 19, 2023, 7:51 AM
actually
const file = e.currentTarget[0].files[0];
is itself the image file contents
Mar 19, 2023, 7:56 AM
Oh that's right, I didn't notice you were fetching that directly. Are you getting the same error?
Mar 19, 2023, 7:57 AM
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
Mar 19, 2023, 7:58 AM
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
Mar 19, 2023, 8:00 AM
You're facing the same issue, which is the lack of a "File" type
Mar 19, 2023, 8:00 AM
You need either a stream or a buffer
Mar 19, 2023, 8:00 AM
const file = e.currentTarget[0].files[0];
=&gt;

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

Mar 19, 2023, 8:01 AM
The Request type also has an arrayBuffer() Promise
Mar 19, 2023, 8:02 AM
then await
req.arrayBuffer()
should work in the backend. Let me cook
Mar 19, 2023, 8:03 AM
Correct
Mar 19, 2023, 8:03 AM
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.
Mar 19, 2023, 8:12 AM
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
Mar 19, 2023, 8:13 AM
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,
Mar 19, 2023, 8:18 AM
2. POST the image data to external API, and upload the image to sanity cdn
i
Mar 19, 2023, 8:19 AM
Okay let me use some random project I have here, give me a second
Mar 19, 2023, 8:19 AM
Ok
Mar 19, 2023, 8:20 AM
If you can solve it, please share me the logic
Mar 19, 2023, 8:24 AM
Naturally. Getting to it now
Mar 19, 2023, 8:24 AM
Okay I think I got it
Mar 19, 2023, 9:02 AM
user U
Add this to your handler:
export const config = {
  api: {
    bodyParser: false
  }
};
Mar 19, 2023, 9:04 AM
Top-level
Mar 19, 2023, 9:04 AM
Then client.assets.upload('image', req.read(read.readableLength))
Mar 19, 2023, 9:05 AM
Client-side stays the same
Mar 19, 2023, 9:05 AM
The issue was Next was trying to automatically parse the body and was ruining it
Mar 19, 2023, 9:05 AM
Now, you're able to upload the image to the sanity cdn?
Mar 19, 2023, 9:11 AM
Correct
Mar 19, 2023, 9:13 AM
Can you please share the both frontend and backend code. I want to see how you're sending the POST data
Mar 19, 2023, 9:15 AM
	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>
	);
Mar 19, 2023, 9:20 AM
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' })
}
Mar 19, 2023, 9:20 AM
Don't mind my response haha
Mar 19, 2023, 9:20 AM
Thanks ♥️.
Mar 19, 2023, 9:22 AM
And coding exists because of John Doe😂
Mar 19, 2023, 9:22 AM
Hahaha did it work for you now?
Mar 19, 2023, 9:23 AM
If it worked for you, It should work on my end also.
Mar 19, 2023, 9:27 AM
Sorry to say, but your code throws premature end of input file error. I tried uploading different images but got the same results.
Mar 19, 2023, 2:13 PM
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
}
Mar 19, 2023, 2:14 PM
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
Mar 19, 2023, 2:38 PM
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.😄
Mar 19, 2023, 8:18 PM
user U
I’m facing the same issue, could you share your solution with us?
May 4, 2023, 8:17 PM
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
😴
May 4, 2023, 9:27 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?