The video uses a specific AI coding tool to demonstrate the setup, but the configuration works 1:1 with Cursor, Claude Code, or any other tool. Despia is web framework and tooling agnostic, so the only thing that matters is passing a publicly accessible image URL into the SDK call.
savethisimage:// is fire-and-forget. The native runtime fetches the image at the URL you pass, writes it to the device’s photo gallery, and returns immediately. The user sees the standard system save confirmation. Your JavaScript keeps running in parallel.
Only HTTPS CDN-hosted URLs are accepted. Data URLs (data:image/png;base64,...), blob URLs (blob:...), and file:// paths will not save. The runtime needs to fetch the image from a real network endpoint, so you have to upload the bytes to a CDN, S3 bucket, or similar storage first and pass the resulting public URL into the call.
Installation
npm install despia-native
import despia from 'despia-native';
<script src="https://cdn.jsdelivr.net/npm/despia-native/index.min.js"></script>
How it works
Pass a publicly accessible HTTPS URL into the url parameter. Encode the URL with encodeURIComponent so query strings and special characters survive the trip through the scheme handler.
import despia from 'despia-native'
const isDespia = navigator.userAgent.toLowerCase().includes('despia')
function saveImage(url) {
if (isDespia) {
despia(`savethisimage://?url=${encodeURIComponent(url)}`)
}
}
saveImage('https://cdn.example.com/photos/sunset.jpg')
The OS handles the photo permission prompt the first time the app saves an image. Subsequent calls go through silently as long as the user granted access.
Save a remote image directly
The simplest case is saving an image you already have a CDN URL for, such as a generated image returned by your backend or a piece of content the user is viewing.
import despia from 'despia-native'
const isDespia = navigator.userAgent.toLowerCase().includes('despia')
function PhotoCard({ photo }) {
function save() {
if (isDespia) {
despia(`savethisimage://?url=${encodeURIComponent(photo.url)}`)
}
}
return (
<div>
<img src={photo.url} alt="" />
<button onClick={save}>Save to camera roll</button>
</div>
)
}
Save a canvas or generated image
Canvas exports and dynamically generated images come back as data URLs or blobs by default, neither of which savethisimage:// accepts. Upload the bytes to your storage layer first, then pass the resulting public URL into the SDK call.
async function saveCanvasToGallery(canvas) {
if (!isDespia) return
// 1. Convert canvas to a Blob (binary, not data URL)
const blob = await new Promise(res => canvas.toBlob(res, 'image/png'))
// 2. Upload the blob to your CDN or storage backend
const formData = new FormData()
formData.append('file', blob, 'export.png')
const res = await fetch('/api/uploads', { method: 'POST', body: formData })
const data = await res.json()
// 3. Pass the resulting public URL into the SDK
despia(`savethisimage://?url=${encodeURIComponent(data.url)}`)
}
The upload step is your code, not Despia’s. Use whatever storage you already have (S3 with a presigned URL, Cloudflare R2, Bunny CDN, Supabase Storage, etc). The output must be a publicly fetchable HTTPS URL.
For images generated by an AI endpoint that already returns a hosted URL (OpenAI DALL-E, Replicate, etc), skip the upload entirely and pass the returned URL straight in. The expensive part is hosting the bytes, and the AI service has already done it for you.
async function saveGeneratedImage(prompt) {
if (!isDespia) return
const res = await fetch('/api/generate', {
method: 'POST',
body: JSON.stringify({ prompt }),
})
const data = await res.json()
// data.url is already a CDN URL from the AI service
despia(`savethisimage://?url=${encodeURIComponent(data.url)}`)
}
Save an image already shown on the page
If the image is already rendered with a CDN-hosted src, you can read the URL straight off the DOM. Do not pass the rendered element, the runtime needs the original network URL.
function saveImageFromElement(imgId) {
if (!isDespia) return
const img = document.getElementById(imgId)
if (!img) return
const url = img.currentSrc || img.src
// sanity check, refuse data: and blob: which the runtime cannot save
if (!url.startsWith('https://')) {
console.warn('savethisimage://requires an HTTPS URL, got', url)
return
}
despia(`savethisimage://?url=${encodeURIComponent(url)}`)
}
The defensive check at the bottom catches the most common bug: someone resolves the image via URL.createObjectURL(blob) for display purposes and then tries to save the resulting blob: URL, which silently fails on the native side.
Resources