Remote files
Upload, browse and reference files stored outside of Sanity Studio.
By Félix Péault
Install command
npm i sanity-plugin-remote-filesRemote Files Plugin for Sanity
Upload, browse and reference files stored outside of Sanity Studio.
Useful for large videos, audio files, archives, PDFs or any file you want to keep in R2, S3 or another storage backend.
Features
- Remote files tool: Browse, search, upload and manage your remote file library.
remoteFilefield type: Upload or select remote files directly from document fields.- File details dialog: Preview files, edit internal titles, inspect metadata and see which documents use a file.
- Cloudflare R2 provider: Worker-backed upload/delete flow with an interactive setup command.
- S3-compatible provider: Express API template for S3 or S3-compatible storage.
- Flexible providers: Use the bundled R2/S3 providers or add your own.
- Backend-only secrets: Provider credentials stay in your Worker/API, not in Sanity documents.
Installation
# npm
npm i sanity-plugin-remote-files
# yarn
yarn add sanity-plugin-remote-files
# pnpm
pnpm add sanity-plugin-remote-files
# bun
bun add sanity-plugin-remote-filesUsage
Add it as a plugin in sanity.config.ts (or .js):
import { defineConfig } from 'sanity'
import { cloudflareR2Provider, remoteFiles } from 'sanity-plugin-remote-files'
export default defineConfig({
// ...
plugins: [
remoteFiles({
tool: {
title: 'Videos',
description: 'Browser, upload and manage videos.',
},
providers: [
cloudflareR2Provider({
id: 'r2',
endpoint: process.env.SANITY_STUDIO_R2_ENDPOINT,
publicUrl: process.env.SANITY_STUDIO_R2_PUBLIC_URL,
uploadPrefix: 'uploads',
headers: {
authorization: `Bearer ${process.env.SANITY_STUDIO_R2_TOKEN}`,
},
}),
],
}),
],
})Then use the remoteFile field type in your schema:
import { defineField, defineType } from 'sanity'
export const page = defineType({
name: 'page',
type: 'document',
fields: [
defineField({
name: 'video',
title: 'Video',
type: 'remoteFile',
options: {
accept: 'video/*',
provider: 'r2',
},
}),
],
})Setup Cloudflare R2
Run the interactive R2 setup after installing the plugin:
npx sanity-plugin-remote-files setup r2 remote-files-r2-workerThe setup will:
- Create or reuse an R2 bucket
- Generate and deploy the Worker used by the plugin
- Configure the public file URL and allowed Studio origins
- Print the Sanity config snippet to add to your Studio
If you only want the Worker files without running Cloudflare setup:
npx sanity-plugin-remote-files setup r2 remote-files-r2-worker --template-onlySetup S3
S3 setup scaffolds a small Express API template. Bucket, IAM and deployment differ between AWS setups, so those stay explicit.
npx sanity-plugin-remote-files setup s3 remote-files-s3-apiOptions
remoteFiles(config)
Main plugin function. Adds the remoteFiles.file document type, the remoteFile field type and the Studio tool.
providers: RemoteFilesProvider[]- configured storage providerstool.name?: string- tool route name. Default:'remote-files'tool.title?: string- tool title. Default:'Remote files'tool.description?: string- text shown at the top of the tool
remoteFile field options
accept?: string- value passed to the native file input, for example'video/*'or'.pdf'.provider?: string- provider id to use for this field. If omitted, the first provider is used.
Provider ids are stored on file documents and make multiple instances possible:
remoteFiles({
providers: [
cloudflareR2Provider({ id: 'marketing-r2', endpoint: process.env.SANITY_STUDIO_MARKETING_R2_ENDPOINT }),
cloudflareR2Provider({ id: 'product-r2', endpoint: process.env.SANITY_STUDIO_PRODUCT_R2_ENDPOINT }),
],
})Adding Providers
Providers are not tied to Cloudflare Workers. The bundled R2 setup uses a Worker because it is a good fit for R2, but other providers can use any backend, SDK, signed URL flow or REST API.
Cloudflare R2 and the S3 template both use the same basic idea: the Studio talks to your Worker/API and that backend talks to storage with the real credentials.
The default backend contract is intentionally small:
POST /upload multipart/form-data with a `file` field
DELETE /files/:key delete a stored objectPOST /upload returns the file metadata the plugin stores in Sanity:
{
key: string
url: string
filename: string
contentType?: string
size?: number
}If another storage provider can follow that contract, adding it is usually just a small provider factory:
import { createRemoteFilesProvider } from 'sanity-plugin-remote-files'
export function myProvider(config: {id: string; endpoint: string; title?: string}) {
return createRemoteFilesProvider(config, {title: 'My Provider'})
}For signed URL flows, use signedUrlProvider with a backend endpoint that creates signed upload URLs and a delete endpoint. For anything else, providers can define custom uploadFile and deleteFile handlers. Keep secrets out of Studio code; prefer calling your own backend for privileged operations.
Querying
Files are stored as remoteFiles.file documents. A remoteFile field stores a reference to one of those documents.
*[_type == 'page']{
title,
video{
asset->{
_id,
title,
filename,
url,
key,
provider,
contentType,
duration,
size,
uploadedAt
}
}
}For a cleaner frontend projection:
*[_type == 'page']{
title,
"video": video.asset->{
"title": coalesce(title, filename),
filename,
url,
contentType,
duration,
size
}
}License
MIT © Félix Péault (Flayks)
Develop & test
This plugin uses @sanity/plugin-kit with default configuration for build & watch scripts.
See Testing a plugin in Sanity Studio on how to run this plugin with hotreload in the studio.

