Skip to main content

Overview

The Image Processing API provides comprehensive image handling capabilities including compression, resizing, validation, and variant generation for different use cases across the platform. Location: convex/shared/imageProcessing.ts

Smart Compression

Automatic image compression with quality optimization

Size Validation

Validates images against type-specific size limits

Variant Generation

Creates multiple image sizes for responsive display

Format Optimization

Converts images to optimized JPEG format

Image Size Limits

The system enforces different size limits based on image type:
  • Max Size: 2MB
  • Max Dimensions: 512x512px
  • Use Case: User profile pictures
  • Max Size: 5MB
  • Max Dimensions: 1920x1080px
  • Use Case: Store banner images
  • Max Size: 10MB
  • Max Dimensions: 2048x2048px
  • Use Case: Document verification
  • Max Size: 5MB
  • Max Dimensions: 1920x1920px
  • Use Case: Product photos (primary and additional)
  • Max Size: 5MB
  • Max Dimensions: 1920x1920px
  • Use Case: Customer review images
  • Max Size: 5MB
  • Max Dimensions: 1920x1920px
  • Use Case: Images shared in conversations

Process Image

Process and optimize an uploaded image with automatic compression and resizing.
storageId
Id<'_storage'>
required
Storage ID of the uploaded image
imageType
'avatar' | 'storeLogo' | 'storeCover' | 'tradeLicense' | 'productPrimary' | 'productAdditional' | 'reviewPhoto' | 'chatImage'
required
Type of image being processed
quality
number
JPEG quality (1-100, default: 80)
const result = await convex.action(api.shared.imageProcessing.processImage, {
  storageId: "file123456789",
  imageType: "productPrimary",
  quality: 85
});
{
  "success": true,
  "originalSize": 2048000,
  "compressedSize": 512000,
  "compressionRatio": 75,
  "width": 1920,
  "height": 1920,
  "format": "jpeg",
  "newStorageId": "file987654321"
}
This function processes images in a Node.js environment using Jimp for image manipulation. The processed image is stored as a new file and the original is preserved.

Validate Image Upload

Validate an uploaded image before processing to ensure it meets size and dimension requirements.
storageId
Id<'_storage'>
required
Storage ID of the uploaded image
imageType
'avatar' | 'storeLogo' | 'storeCover' | 'tradeLicense' | 'productPrimary' | 'productAdditional' | 'reviewPhoto' | 'chatImage'
required
Type of image being validated
const validation = await convex.action(api.shared.imageProcessing.validateImageUpload, {
  storageId: "file123456789",
  imageType: "avatar"
});
{
  "valid": true,
  "sizeInMB": 1.5,
  "width": 512,
  "height": 512
}
{
  "valid": false,
  "error": "Image size (6.2MB) exceeds maximum allowed size (5MB)",
  "sizeInMB": 6.2
}

Create Image Variants

Create multiple image variants (thumbnails, medium, large) for responsive display.
storageId
Id<'_storage'>
required
Storage ID of the source image
variants
Array<object>
required
Array of variant configurations
variants[].name
string
required
Name for the variant (e.g., “thumbnail”, “medium”, “large”)
variants[].width
number
required
Target width in pixels
variants[].height
number
Target height in pixels (optional - maintains aspect ratio if not provided)
variants[].quality
number
JPEG quality for this variant (default: 80)
const result = await convex.action(api.shared.imageProcessing.createImageVariants, {
  storageId: "file123456789",
  variants: [
    {
      name: "thumbnail",
      width: 150,
      height: 150,
      quality: 70
    },
    {
      name: "medium",
      width: 400,
      quality: 80
    },
    {
      name: "large",
      width: 800,
      quality: 85
    }
  ]
});
{
  "success": true,
  "variants": [
    {
      "name": "thumbnail",
      "storageId": "file111222333",
      "width": 150,
      "height": 150,
      "size": 12500
    },
    {
      "name": "medium",
      "storageId": "file444555666",
      "width": 400,
      "height": 300,
      "size": 45600
    },
    {
      "name": "large",
      "storageId": "file777888999",
      "width": 800,
      "height": 600,
      "size": 128000
    }
  ]
}

Complete Image Upload Flow

Here’s a complete example of uploading and processing an image:
import { useAction } from 'convex/react';
import { api } from './convex/_generated/api';
import * as ImagePicker from 'expo-image-picker';

function useImageUpload() {
  const validateImage = useAction(api.shared.imageProcessing.validateImageUpload);
  const processImage = useAction(api.shared.imageProcessing.processImage);
  const createVariants = useAction(api.shared.imageProcessing.createImageVariants);

  const uploadProductImage = async (imageType: 'productPrimary' | 'productAdditional') => {
    try {
      // 1. Pick image from gallery
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [1, 1],
        quality: 1,
      });

      if (!result.assets?.[0]) {
        throw new Error('No image selected');
      }

      // 2. Upload to Convex storage
      const response = await fetch(result.assets[0].uri);
      const blob = await response.blob();
      const storageId = await convex.storage.store(blob);

      // 3. Validate image
      const validation = await validateImage({
        storageId,
        imageType
      });

      if (!validation.valid) {
        throw new Error(validation.error);
      }

      // 4. Process and optimize image
      const processed = await processImage({
        storageId,
        imageType,
        quality: 85
      });

      // 5. Create variants for responsive display
      const variants = await createVariants({
        storageId: processed.newStorageId,
        variants: [
          { name: "thumbnail", width: 150, height: 150, quality: 70 },
          { name: "medium", width: 400, quality: 80 },
          { name: "large", width: 800, quality: 85 }
        ]
      });

      return {
        originalStorageId: storageId,
        processedStorageId: processed.newStorageId,
        variants: variants.variants,
        compressionStats: {
          originalSize: processed.originalSize,
          compressedSize: processed.compressedSize,
          compressionRatio: processed.compressionRatio
        }
      };

    } catch (error) {
      console.error('Image upload failed:', error);
      throw error;
    }
  };

  return { uploadProductImage };
}

Image Type Guidelines

Best Practices:
  • Use square aspect ratio (1:1)
  • High quality (90+) for user-facing images
  • Consider adding border radius in UI
Use Cases:
  • Customer profile pictures
  • Store owner avatars
Best Practices:
  • Use square or 4:3 aspect ratio
  • Good lighting and clear product visibility
  • Multiple angles for better representation
Use Cases:
  • Product catalog images
  • Featured product displays
Best Practices:
  • Logo: Simple, recognizable at small sizes
  • Cover: Wide format, represents store theme
  • Consistent with brand colors
Use Cases:
  • Store identification
  • Marketing materials
Best Practices:
  • High resolution for text readability
  • Good contrast and lighting
  • Full document visible in frame
Use Cases:
  • Trade license verification
  • Identity documents

Error Handling

Status Code: 400
{
  "error": "Image size (6.2MB) exceeds maximum allowed size (5MB)"
}
Status Code: 400
{
  "error": "Unsupported image format"
}
Status Code: 500
{
  "error": "Failed to process image: Invalid image data"
}
Status Code: 404
{
  "error": "Image not found in storage"
}

Performance Tips

Compression Quality

Use quality 70-80 for thumbnails, 85-90 for main images

Variant Sizes

Create variants only for sizes you actually use

Batch Processing

Process multiple images in parallel when possible

Storage Cleanup

Clean up original files after processing if not needed
All image processing functions run in a Node.js environment and may take several seconds to complete for large images. Consider showing loading indicators to users.
Always validate images before processing to provide immediate feedback to users about size and format issues.