
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI 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:
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)
}
}<input type="file" accept=".pdf,image/*" onChange={handleFileUpload} />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.
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
})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>
)
}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.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store