How to Add an Image to a Payload

10 replies
Last updated: Aug 17, 2020
I am able to send some data to sanity via this https://www.sanity.io/docs/js-client and the create function I want to add an image, it is by url this is what I have so far
.create({
          _type: "products",
          name: payload.data.object.name,
          sku: payload.data.object.id,
          price: payload.data.object.price,
          image: payload.data.object.images[0],
        })
do I need to upload the image or is there a way to add it with create?
AI Update

Looking at your code, you have the image URL but need to properly upload it to Sanity. You can't just pass the URL directly to create() - you need to upload the image first and then reference it.

Upload the image first, then reference it

Based on the Sanity documentation on uploading assets, you need to:

  1. Fetch and upload the image using client.assets.upload()
  2. Get the asset reference from the upload response
  3. Create your document with the image reference

Here's a complete example:

import {Readable} from 'node:stream'

async function uploadImageFromUrl(url) {
  const {body} = await fetch(url)
  return client.assets.upload('image', Readable.fromWeb(body), {
    filename: url.split('/').pop() // optional: extract filename from URL
  })
}

// Then in your create function:
const imageUrl = payload.data.object.images[0]
const uploadedImage = await uploadImageFromUrl(imageUrl)

await client.create({
  _type: "products",
  name: payload.data.object.name,
  sku: payload.data.object.id,
  price: payload.data.object.price,
  image: {
    _type: 'image',
    asset: {
      _type: 'reference',
      _ref: uploadedImage._id
    }
  }
})

Important considerations

Error handling: Always check if the image URL exists and handle fetch failures:

async function uploadImageFromUrl(url) {
  if (!url) return null
  
  try {
    const response = await fetch(url)
    if (!response.ok) return null
    
    return client.assets.upload('image', Readable.fromWeb(response.body))
  } catch (error) {
    console.error('Failed to upload image:', error)
    return null
  }
}

Avoid duplicate uploads: Sanity creates deterministic IDs based on image content, so uploading the same image multiple times won't create duplicates. However, you can optimize by tracking uploaded images with metadata using the source parameter to avoid unnecessary re-uploads during migrations.

Rate limiting: If you're uploading many images, use a library like p-limit to control concurrent uploads and avoid hitting API rate limits.

The key takeaway: you must upload the image asset first using client.assets.upload(), then reference that uploaded asset's _id in your document's image field. The JS client documentation provides more details on using the client methods.

Is
payload.data.object.images[0]
a URL?If so you need to upload that file to sanity first then use the returned object as your
image
data. Here's an example https://www.sanity.io/guides/guide-importing-data-from-external-sources#importing-assets-images-and-files-e6b7d1e8a150
ok thanks that's the case I will try this out
user J
after trying this I get
Content has invalid type: sanity.imageAsset
and the current value looks like this

{
  "_id": "image-9b609eb04af8b75c6d445c802ddaed1364893564-371x757-png",
  "_type": "sanity.imageAsset",
  "assetId": "9b609eb04af8b75c6d445c802ddaed1364893564",
  "extension": "png",
  "metadata": {
    "_type": "sanity.imageMetadata",
    "dimensions": {
      "_type": "sanity.imageDimensions",
      "aspectRatio": 0.4900924702774108,
      "height": 757,
      "width": 371
    },
    "hasAlpha": true,
    "isOpaque": true,
    "lqip": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAApCAYAAAA1bQl+AAAACXBIWXMAAAsSAAALEgHS3X78AAAC80lEQVRIx+2W63KqQBCE8/4PkxOjRhABr4CggJqqPM5mvpGlQC5JTv7mxxS7C/T2TPcOPJVlaU6nk0nTTCI1RwmuWZZppI15URTmer2a2+1WX5vjj48P81TkuYnj2Oz2e7PbETuNvcwPh0O1LnMZs/GXgKXsejweTRRFCmDDAlnwRJ75HsMKEBBe1JDxZrMxQRCa9Xqj4KR8uVxqgEFAHsolbdKxdbvXNNWNuDIfYtcBZPD+/q7x+FBf3JrjPkB7sywvyqIoSoPyMH9k0sdutIYE4iRJYs7ncyvFJqvRlGGjgKpypIDMqesQwChDJqQHUxWlEsCmPFjLBlAH0C7C6iwxVL/bF8B1yqUIAStrHeoHeDEiTl9tax9aYTCzHwTG930ThKGekiiK9Z4txZADsF2rhjALBWT+9mams5mZz+fmbbEwC8fRseO6Zi0b6hkX4TB8c4OWsQluoLDjuBorYbkQwNfXV/P8/M+8vEx0A4Dd5dKE63VtsSw7KWhHFFJbep5ZVi9wnVVMV6uVlGRbNwvA6FLxV4CeAMLMdZfGExACsP3+UDugGZ2UW4CymyspzWZzBV16FtAXNkn9sr1aoI4oLHAThrBBCNKFJekCSj/EVnES39PVVDMF73QbFlCMAm+2W+mDgYbvB8ouDNdar+MxrRsxdkobPbIDmOdnLTb+w4c2YEwZAOYTYRsItsnzouXJRspXpc7DpIktYHev58xMJhPx5lzWPRXN8+6KJ8mxar5lvyikDBixFtsAOJ1O1djYidRZJ7ZiH7oTteSY9qtc2cYRUSwTwBnjP45frk34HgDlVWcaAExUXXvsYEXtiEhEsYqOdpteQDnPsFwsHD0pAFMvuhBZUBpEYd4RxYKxe5alZiu2oUkQnGfSpinoH4TYhNQ3VZPARrzXso0F03pUwc4ECtreqH1TorluP62dBqvNtChGm+m3vyn2bI59P777bWl9lx93+wnTjiiP/ys/TbuX4f+CDTL8TfwB/gH+EvATRhjoE2s1m1UAAAAASUVORK5CYII=",
    "palette": {
      "_type": "sanity.imagePalette",
      "darkMuted": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#717171",
        "foreground": "#fff",
        "population": 1.7,
        "title": "#fff"
      },
      "darkVibrant": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#424242",
        "foreground": "#fff",
        "population": 0,
        "title": "#fff"
      },
      "dominant": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#717171",
        "foreground": "#fff",
        "population": 1.7,
        "title": "#fff"
      },
      "lightMuted": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#bcbcbc",
        "foreground": "#000",
        "population": 1.34,
        "title": "#fff"
      },
      "lightVibrant": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#bcbcbc",
        "foreground": "#000",
        "population": 0,
        "title": "#fff"
      },
      "muted": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#848484",
        "foreground": "#fff",
        "population": 0.84,
        "title": "#fff"
      },
      "vibrant": {
        "_type": "sanity.imagePaletteSwatch",
        "background": "#7f7f7f",
        "foreground": "#fff",
        "population": 0,
        "title": "#fff"
      }
    }
  },
  "mimeType": "image/png",
  "originalFilename": "9b609eb04af8b75c6d445c802ddaed1364893564-371x757.png",
  "path": "images/9pb8ktqh/production/9b609eb04af8b75c6d445c802ddaed1364893564-371x757.png",
  "sha1hash": "9b609eb04af8b75c6d445c802ddaed1364893564",
  "size": 50712,
  "uploadId": "iIit9c6kVfX0QnbI0JYuMdUHHJ5NwpXV",
  "url": "<https://cdn.sanity.io/images/9pb8ktqh/production/9b609eb04af8b75c6d445c802ddaed1364893564-371x757.png>"
}
any idea what's going on
Sorry, my bad. You image data should be reference to the image asset. So this should do it:
{
  _type: "image"
  asset:{
   _ref: "the-_id-of-the-returned-asset"
   _type:"reference"
  }
}
I've done something like this there is already a _type: "products in the create
fetch(payload.data.object.images[0])
        .then((res) => res.buffer())
        .then((buffer) => client.assets.upload("image", buffer))
        .then(async (assetDocument) => {
          const result = await client
            .create({
              _type: "products",
              name: payload.data.object.name,
              sku: payload.data.object.id,
              price: payload.data.object.price,
              image: assetDocument,
            })
            .then(() => ({
              statusCode: 200,
              body: payload.message,
            }))
            .catch((error) => ({
              statusCode: 422,
              body: `Oops! Something went wrong. ${error}`,
            }));
        });
do I do a second create with the image type and add those references, so no need to place the current create inside the fetch.
image: {
  _type: "image"
  asset:{
   _ref: assetDocument._id
   _type:"reference"
  }
}
should do it then
ok great ill try that thanks
Awesome worked thanks

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?