> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Utilities

> Essential utility functions for file uploads and system operations

## Overview

The Utilities API provides essential system functions including secure file upload URL generation and other utility operations needed across the platform.

**Location:** `convex/shared/utils.ts`

## Generate Upload URL

Generates a secure upload URL for file uploads to Convex storage.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const uploadUrl = await convex.mutation(api.shared.utils.generateUploadUrl);

  // Use the URL to upload a file
  const file = new File(['content'], 'example.jpg', { type: 'image/jpeg' });

  const response = await fetch(uploadUrl, {
    method: 'POST',
    body: file
  });

  const { storageId } = await response.json();
  console.log('File uploaded with ID:', storageId);
  ```

  ```javascript JavaScript theme={null}
  const uploadUrl = await convex.mutation("shared.utils.generateUploadUrl");

  // Upload file using the URL
  const fileInput = document.getElementById('fileInput');
  const file = fileInput.files[0];

  const response = await fetch(uploadUrl, {
    method: 'POST',
    body: file
  });

  const { storageId } = await response.json();
  ```

  ```python Python theme={null}
  import requests

  # Get upload URL
  response = requests.post(
      f"{CONVEX_URL}/api/mutation",
      json={
          "path": "shared.utils.generateUploadUrl",
          "args": {}
      },
      headers={"Authorization": f"Bearer {token}"}
  )

  upload_url = response.json()

  # Upload file
  with open('image.jpg', 'rb') as f:
      upload_response = requests.post(upload_url, files={'file': f})
      storage_id = upload_response.json()['storageId']
  ```

  ```curl cURL theme={null}
  # Get upload URL
  curl -X POST "https://your-deployment.convex.cloud/api/mutation" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -d '{
      "path": "shared.utils.generateUploadUrl",
      "args": {}
    }'

  # Upload file (use the returned URL)
  curl -X POST "UPLOAD_URL_FROM_RESPONSE" \
    -F "file=@image.jpg"
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  "https://upload.convex.dev/upload/abc123def456?token=xyz789"
  ```
</ResponseExample>

## File Upload Process

The file upload process follows these steps:

<Steps>
  <Step title="Generate Upload URL">
    Call `generateUploadUrl` to get a secure, temporary upload URL
  </Step>

  <Step title="Upload File">
    Use the returned URL to upload your file via HTTP POST
  </Step>

  <Step title="Get Storage ID">
    The upload response contains a `storageId` that you can use to reference the file
  </Step>

  <Step title="Use Storage ID">
    Use the `storageId` in other API calls (e.g., creating products with images)
  </Step>
</Steps>

## Supported File Types

<CardGroup cols={2}>
  <Card title="Images" icon="image">
    **Formats**: JPEG, PNG, WebP, GIF, SVG\
    **Max Size**: 10MB\
    **Recommended**: WebP for web, PNG for transparency
  </Card>

  <Card title="Documents" icon="file-text">
    **Formats**: PDF, DOC, DOCX, TXT\
    **Max Size**: 25MB\
    **Use Case**: Trade licenses, contracts, receipts
  </Card>

  <Card title="Videos" icon="video">
    **Formats**: MP4, WebM, MOV\
    **Max Size**: 100MB\
    **Use Case**: Product demos, store tours
  </Card>

  <Card title="Audio" icon="volume">
    **Formats**: MP3, WAV, OGG\
    **Max Size**: 25MB\
    **Use Case**: Voice notes, audio descriptions
  </Card>
</CardGroup>

## Complete Upload Example

Here's a complete example showing how to upload a file and use it:

<CodeGroup>
  ```typescript React Component theme={null}
  import { useState } from 'react';
  import { useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  function FileUploadComponent() {
    const [uploading, setUploading] = useState(false);
    const [storageId, setStorageId] = useState<string | null>(null);
    
    const generateUploadUrl = useMutation(api.shared.utils.generateUploadUrl);
    const createProduct = useMutation(api.shared.products.createProduct);

    const handleFileUpload = async (file: File) => {
      try {
        setUploading(true);
        
        // Step 1: Generate upload URL
        const uploadUrl = await generateUploadUrl();
        
        // Step 2: Upload file
        const response = await fetch(uploadUrl, {
          method: 'POST',
          body: file
        });
        
        const { storageId } = await response.json();
        setStorageId(storageId);
        
        // Step 3: Use storage ID in product creation
        await createProduct({
          name: "New Product",
          category: "k123456789",
          price: 19.99,
          isActive: true,
          primaryImage: storageId // Use the uploaded file
        });
        
        console.log('Product created with uploaded image!');
        
      } catch (error) {
        console.error('Upload failed:', error);
      } finally {
        setUploading(false);
      }
    };

    return (
      <div>
        <input
          type="file"
          accept="image/*"
          onChange={(e) => {
            const file = e.target.files?.[0];
            if (file) handleFileUpload(file);
          }}
          disabled={uploading}
        />
        {uploading && <p>Uploading...</p>}
        {storageId && <p>Upload successful! ID: {storageId}</p>}
      </div>
    );
  }
  ```

  ```javascript Vanilla JS theme={null}
  async function uploadFile(fileInput) {
    const file = fileInput.files[0];
    if (!file) return;

    try {
      // Step 1: Generate upload URL
      const uploadUrlResponse = await fetch('/api/generate-upload-url', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`
        }
      });
      const { uploadUrl } = await uploadUrlResponse.json();

      // Step 2: Upload file
      const formData = new FormData();
      formData.append('file', file);

      const uploadResponse = await fetch(uploadUrl, {
        method: 'POST',
        body: file
      });

      const { storageId } = await uploadResponse.json();
      console.log('File uploaded successfully:', storageId);

      return storageId;

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

  // Usage
  document.getElementById('fileInput').addEventListener('change', async (e) => {
    const storageId = await uploadFile(e.target);
    // Use storageId in your application
  });
  ```
</CodeGroup>

## Security & Best Practices

<AccordionGroup>
  <Accordion title="File Validation">
    Always validate file types and sizes on the client side before uploading:

    ```typescript theme={null}
    function validateFile(file: File): boolean {
      // Check file size (10MB limit for images)
      if (file.size > 10 * 1024 * 1024) {
        throw new Error('File too large. Maximum size is 10MB.');
      }
      
      // Check file type
      const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
      if (!allowedTypes.includes(file.type)) {
        throw new Error('Invalid file type. Only JPEG, PNG, and WebP are allowed.');
      }
      
      return true;
    }
    ```
  </Accordion>

  <Accordion title="Upload URL Expiration">
    Upload URLs expire after 1 hour for security. Generate a new URL if upload fails:

    ```typescript theme={null}
    async function uploadWithRetry(file: File, maxRetries = 3) {
      for (let i = 0; i < maxRetries; i++) {
        try {
          const uploadUrl = await generateUploadUrl();
          const response = await fetch(uploadUrl, {
            method: 'POST',
            body: file
          });
          
          if (response.ok) {
            return await response.json();
          }
        } catch (error) {
          if (i === maxRetries - 1) throw error;
          console.warn(`Upload attempt ${i + 1} failed, retrying...`);
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Progress Tracking">
    Track upload progress for better user experience:

    ```typescript theme={null}
    async function uploadWithProgress(file: File, onProgress: (progress: number) => void) {
      const uploadUrl = await generateUploadUrl();
      
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener('progress', (e) => {
          if (e.lengthComputable) {
            const progress = (e.loaded / e.total) * 100;
            onProgress(progress);
          }
        });
        
        xhr.addEventListener('load', () => {
          if (xhr.status === 200) {
            resolve(JSON.parse(xhr.responseText));
          } else {
            reject(new Error('Upload failed'));
          }
        });
        
        xhr.addEventListener('error', () => reject(new Error('Upload failed')));
        
        xhr.open('POST', uploadUrl);
        xhr.send(file);
      });
    }
    ```
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Upload URL generation failed">
    **Status Code**: `500`

    ```json theme={null}
    {
      "error": "Failed to generate upload URL. Please try again."
    }
    ```
  </Accordion>

  <Accordion title="File upload failed">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "File upload failed",
      "details": "File size exceeds maximum limit"
    }
    ```
  </Accordion>

  <Accordion title="Invalid file type">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Invalid file type",
      "allowedTypes": ["image/jpeg", "image/png", "image/webp"]
    }
    ```
  </Accordion>
</AccordionGroup>

<Note>
  Upload URLs are temporary and expire after 1 hour. Always generate a fresh URL for each upload operation.
</Note>

<Tip>
  For better performance, compress images before uploading and use WebP format when possible for web applications.
</Tip>
