How to upload files to Sanity using file picker without local file path?

8 replies
Last updated: Jan 28, 2026
I never knew how difficult file uploads could be. My goal is to allow users of my app to upload assets (PDF or images) through a file picker. I want to upload the file to sanity, return the file URL and name in the response, and save the file name and URL into my database so that they can be downloaded or deleted in the future. Is there a tutorial to follow? I’ve read https://www.sanity.io/docs/http-api-assets over and over again, but I do not see a use case where it would work because of the following line of code:
const filePath = '/Users/mike/images/bicycle.jpg'
File path is not available with a file picker. Any direction or ideas would be great.
AI Update

I totally understand your frustration! The HTTP API docs showing a file path example is confusing when you're working with browser file inputs. Good news: you don't need to use file paths at all when uploading from the browser.

The key is using the Sanity JavaScript client with its client.assets.upload() method, which accepts a browser File object directly. Here's how to do it:

Basic Browser Upload Example

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  token: 'your-write-token', // Important: needs write permissions
  apiVersion: '2024-01-01',
  useCdn: false
})

// Handle file input change
async function handleFileUpload(event) {
  const file = event.target.files[0]
  
  if (!file) return
  
  try {
    // Upload the file directly - works with File object from input!
    const asset = await client.assets.upload('file', file, {
      filename: file.name
    })
    
    // Response contains everything you need:
    console.log('File URL:', asset.url)
    console.log('File name:', asset.originalFilename)
    console.log('Asset ID:', asset._id)
    
    // Save to your database
    await saveToYourDatabase({
      url: asset.url,
      filename: asset.originalFilename,
      assetId: asset._id
    })
    
  } catch (error) {
    console.error('Upload failed:', error)
  }
}

HTML Setup

<input type="file" accept=".pdf,image/*" onChange={handleFileUpload} />

Important Setup Notes

1. Authentication Token: You need a token with write permissions. Create one in your Sanity project settings under API → Tokens.

2. CORS Configuration: If uploading from the browser, add your domain to the CORS origins in your project settings (Sanity Manage → API → CORS Origins).

3. File Size Limits: Sanity has a default upload limit of around 200MB for most plans, but check your specific plan limits.

For Images vs Other Files

The method is slightly different depending on file type:

// For images
const imageAsset = await client.assets.upload('image', file, {
  filename: file.name
})

// For PDFs and other files
const fileAsset = await client.assets.upload('file', file, {
  filename: file.name
})

Complete React Example

import {useState} from 'react'
import {createClient} from '@sanity/client'

const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  token: process.env.SANITY_WRITE_TOKEN,
  apiVersion: '2024-01-01',
  useCdn: false
})

function FileUploader() {
  const [uploading, setUploading] = useState(false)
  const [uploadedFile, setUploadedFile] = useState(null)

  const handleUpload = async (e) => {
    const file = e.target.files[0]
    if (!file) return

    setUploading(true)
    
    try {
      const asset = await client.assets.upload('file', file, {
        filename: file.name
      })
      
      setUploadedFile({
        url: asset.url,
        filename: asset.originalFilename,
        id: asset._id
      })
      
      // Save to your database here
      
    } catch (error) {
      console.error('Upload failed:', error)
    } finally {
      setUploading(false)
    }
  }

  return (
    <div>
      <input 
        type="file" 
        onChange={handleUpload}
        disabled={uploading}
      />
      {uploading && <p>Uploading...</p>}
      {uploadedFile && (
        <div>
          <p>Uploaded: {uploadedFile.filename}</p>
          <a href={uploadedFile.url} target="_blank">View File</a>
        </div>
      )}
    </div>
  )
}

Using from Within Sanity Studio

If you're building a custom component inside Sanity Studio, you can use the useClient hook instead:

import {useClient} from 'sanity'

// Inside a React component
const client = useClient({apiVersion: '2025-02-19'})

const file = event.target.files[0]
client.assets
  .upload('file', file)
  .then((document) => {
    console.log('The file was uploaded!', document)
  })
  .catch((error) => {
    console.error('Upload failed:', error.message)
  })

The confusion in the HTTP API docs comes from showing a Node.js example with fs.createReadStream(). In the browser, you simply pass the File object from the file input directly - no file paths needed!

For downloading files later, you can use the URL with the ?dl parameter: ${fileUrl}?dl to force download instead of opening in the browser.

Show original thread
8 replies

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?