Open docs navigation
Implementation

Updated March 31, 2026

8 min read

Image Upload & Storage

Upload, manage, and optimize images. Understand storage limits, quotas, and best practices.

image uploadstorage limitsmedia managementfile uploadstorage quota

Handover provides built-in image storage with automatic quota management. This guide covers the upload flow, storage limits, and best practices.

Upload Flow

The uploadImage() SDK method handles the complete three-step upload process automatically: request upload URL, upload to storage, save metadata.

const fileInput = document.querySelector("input[type=file]");
const file = fileInput.files[0];

try {
  const result = await handover.uploadImage(
    file,
    sessionToken,
    "Hero image for homepage" // optional alt text
  );
  
  console.log("Image uploaded:", result.url);
  console.log("Image ID:", result.imageId);
} catch (error) {
  if (error.code === "STORAGE_LIMIT_EXCEEDED") {
    // Show upgrade prompt
  }
}
typescript

Use Handover keys in generated sites

Uploaded images now expose a stable Handover key in the dashboard. Copy the key rather than the raw URL when you want an AI handover or generated starter to reference the asset.

The public content payload includes imagesByKey so sites can render the latest uploaded file for that key without hardcoding a storage URL.

const content = await handover.getContent();
const hero = content.imagesByKey?.["homepage.hero.image"];

return <img src={hero?.url ?? "/fallback-hero.jpg"} alt={hero?.altText ?? "Hero image"} />;
typescript

Storage Limits

Handover enforces storage quotas based on subscription tier. Uploads that exceed the limit are automatically rejected.

  • Free tier: 100MB total storage
  • Pro tier: 10GB total storage
  • Storage is tracked per user across all projects
  • Deleting images reclaims quota immediately

Supported File Types

Only image files are accepted. The SDK validates the MIME type before uploading.

  • JPEG/JPG (image/jpeg)
  • PNG (image/png)
  • GIF (image/gif)
  • WebP (image/webp)
  • SVG (image/svg+xml)

Deleting Images

Remove unused images to free up storage quota. Deletion is immediate and cannot be undone.

await handover.deleteImage(imageId, sessionToken);
// Storage quota is updated immediately
typescript

Listing Images

Get all images for the project via getContent(). Each image includes URL, ID, key, local path, filename, and metadata when available.

const content = await handover.getContent();

content.images.forEach(image => {
  console.log(image.key);       // Stable Handover key
  console.log(image.url);       // CDN URL
  console.log(image._id);       // Image ID
  console.log(image.localPath); // Original deploy path when pushed by CLI
});
typescript

Best Practices

Follow these guidelines to optimize storage usage and performance.

  • Compress images before upload (use tools like TinyPNG)
  • Use WebP format for better compression
  • Delete unused images regularly
  • Always provide alt text for accessibility
  • Consider lazy loading for multiple images
  • Use responsive images with srcset
Source doc: convex/images.ts