# Cloudinary Image upload and transformations - Package: @bijectionhq/cloudinary - Category: Media - Status: Preview - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/cloudinary/tree/3897f008668b38b4326b484a2ef2789f4b2c05ed - Upstream: https://github.com/imaxisXD/cloudinary-convex/tree/937e5977fd2bdc0dbdf1a83b24653caa118ad42c - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/cloudinary ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/cloudinary.git cd cloudinary git checkout 3897f008668b38b4326b484a2ef2789f4b2c05ed ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @imaxis/cloudinary-convex; check the Bijection adaptation separately. This component integrates Cloudinary's image management APIs directly into Convex, providing both base64 uploads for small files and direct uploads that bypass Convex's 16MB limit for large files up to 100MB. It automatically tracks assets in your Convex database with optimized indexes, generates secure upload signatures server-side, and provides full TypeScript support with exported validators. The component offers both a high-level `makeCloudinaryAPI` approach for React apps and a lower-level `CloudinaryClient` for custom server logic. ### Use cases • Building a photo sharing app where users upload high-resolution images that exceed typical serverless limits • Creating a content management system that needs real-time asset tracking synchronized between Cloudinary and your database • Implementing dynamic image transformations with type-safe parameters for e-commerce product galleries • Building an admin dashboard that requires secure server-side image uploads with progress tracking • Developing a social media platform where you need both small profile pictures and large media uploads ### How it works The component provides two main integration patterns: `makeCloudinaryAPI` creates public functions in your Convex app that React clients can call directly, while `CloudinaryClient` gives you fine-grained control within Convex actions and queries. For uploads, it supports both base64 encoding for files under 10MB and direct uploads to Cloudinary that bypass Convex entirely for larger files. Asset metadata gets automatically stored in Convex with optimized indexes, allowing you to use `useQuery` for real-time asset lists and management. The component includes React hooks like `useCloudinaryUpload` and `useCloudinaryImage` that work with your app's API functions, providing upload progress tracking and transformation capabilities. Setup requires adding the component to your `convex.config.ts` with `app.use(cloudinary)` and configuring your Cloudinary credentials as environment variables. The component handles secure signature generation server-side and provides full TypeScript support with validators like `vAssetResponse` for type-safe data flow. ### Upstream README # Cloudinary Component for Convex - **[Example Repo (Studio App)](https://github.com/imaxisXD/cloudinary-convex-studio)** A Convex Component for Cloudinary integration that provides image upload, transformation, and management capabilities using direct Cloudinary REST APIs with full TypeScript support. ## Features - **Direct API Integration**: Uses Cloudinary REST APIs directly instead of SDKs for better control and reduced dependencies. - **Two Upload Methods**: - **Base64 Upload**: Simple flow for small files (<10MB). - **Direct Upload**: Bypasses Convex 16MB limit for large files (100MB+) with real-time progress. - **Asset Management**: Automatic asset tracking in Convex with optimized indexes. - **Dynamic Transformations**: Generate transformed URLs on-the-fly. - **Type Safety**: Full TypeScript support with exported validators (`vAssetResponse`) and inferred types. - **Secure**: Server-side signature generation and environment-based credentials. ## Architecture ```mermaid graph TD Client["Client Browser"] -->|"Action/Query"| Convex["Convex Component"] Convex -->|"API Call"| Cloudinary["Cloudinary API"] Client -->|"Direct Upload (Large Files)"| Cloudinary Cloudinary -->|"Webhook/Response"| Convex ``` _Simple and robust data flow ensuring your assets are always in sync with your database._ ## Prerequisites 1. **Convex Project**: An existing Convex project ([Setup Guide](https://docs.convex.dev/get-started)) 2. **Cloudinary Account**: Free account at [cloudinary.com](https://cloudinary.com) ## Installation ```bash npm install @imaxis/cloudinary-convex ``` ## Setup ### 1. Configure Convex Create or update your `convex.config.ts`: ```ts // convex/convex.config.ts import { defineApp } from "convex/server"; import cloudinary from "@imaxis/cloudinary-convex/convex.config"; const app = defineApp(); app.use(cloudinary); export default app; ``` ### 2. Set Environment Variables Get credentials from [Cloudinary Console](https://cloudinary.com/console): ```bash npx convex env set CLOUDINARY_CLOUD_NAME npx convex env set CLOUDINARY_API_KEY npx convex env set CLOUDINARY_API_SECRET ``` ## Quick Start ### Option 1: Using makeCloudinaryAPI (Recommended for React Apps) This approach creates public API functions that can be called from React clients. **Backend (convex/cloudinary.ts):** ```ts import { makeCloudinaryAPI } from "@imaxis/cloudinary-convex"; import { components } from "./_generated/api"; // Export all API functions - uses environment variables automatically export const { upload, transform, deleteAsset, listAssets, getAsset, updateAsset, generateUploadCredentials, finalizeUpload, } = makeCloudinaryAPI(components.cloudinary); ``` **React Client:** ```tsx import { api } from "../convex/_generated/api"; import { useQuery, useAction } from "convex/react"; import { useCloudinaryUpload } from "@imaxis/cloudinary-convex/react"; function ImageGallery() { // List images const images = useQuery(api.cloudinary.listAssets, { limit: 20 }); // Upload with progress tracking const { upload, isUploading, progress } = useCloudinaryUpload( api.cloudinary.upload ); const handleUpload = async (file: File) => { const base64 = await fileToBase64(file); const result = await upload(base64, { folder: "uploads" }); console.log("Uploaded:", result.secureUrl); }; return (
{isUploading &&

Uploading... {progress}%

} {images?.map((img) => ( ))}
); } ``` ### Option 2: Using CloudinaryClient (For Server-Side Logic) This approach is ideal when you need more control or want to build custom logic. ```ts // convex/images.ts import { action, query } from "./_generated/server"; import { components } from "./_generated/api"; import { CloudinaryClient, vAssetResponse } from "@imaxis/cloudinary-convex"; import { v } from "convex/values"; // Initialize client - uses environment variables automatically const cloudinary = new CloudinaryClient(components.cloudinary); // Upload Action (Base64) export const uploadImage = action({ args: { base64Data: v.string() }, handler: async (ctx, args) => { return await cloudinary.upload(ctx, args.base64Data, { folder: "uploads", tags: ["user-content"], }); }, }); // List Images Query export const getImages = query({ args: {}, returns: v.array(vAssetResponse), handler: async (ctx) => { return await cloudinary.list(ctx, { limit: 20 }); }, }); ``` ## React Hooks The component provides React hooks for common operations. **Important:** These hooks require function references from your app's API (created via `makeCloudinaryAPI`), not the component directly. ### Available Hooks ```tsx import { useCloudinaryUpload, useCloudinaryImage, useCloudinaryAssets, useCloudinaryAsset, useCloudinaryOperations, useUploadStatus, usePendingUploads, } from "@imaxis/cloudinary-convex/react"; ``` ### Upload Hook ```tsx import { api } from "../convex/_generated/api"; function UploadButton() { const { upload, isUploading, progress, error, reset } = useCloudinaryUpload( api.cloudinary.upload ); const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { try { const result = await upload(file, { folder: "uploads" }); console.log("Upload complete:", result); } catch (err) { console.error("Upload failed:", err); } } }; return (
{isUploading &&

Uploading... {progress}%

} {error &&

{error}

}
); } ``` ### Image Transformation Hook ```tsx function TransformedImage({ publicId }: { publicId: string }) { const { transformedUrl, isLoading } = useCloudinaryImage( api.cloudinary.transform, publicId, { width: 300, height: 300, crop: "fill" } ); if (isLoading) return
Loading...
; return ; } ``` ### Asset Operations Hook ```tsx function AssetManager({ publicId }: { publicId: string }) { const { deleteAsset, updateAsset } = useCloudinaryOperations( api.cloudinary.deleteAsset, api.cloudinary.updateAsset ); const handleDelete = async () => { await deleteAsset(publicId); }; const handleUpdateTags = async () => { await updateAsset(publicId, { tags: ["featured", "hero"] }); }; return (
); } ``` ## Using with Cloudinary React SDK For advanced image rendering features like responsive images, lazy loading, and automatic format optimization, we recommend using Cloudinary's official React SDK alongside this component. **Install Cloudinary React SDK:** ```bash npm install @cloudinary/react @cloudinary/url-gen ``` **Use your component for data, Cloudinary SDK for rendering:** ```tsx import { useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; import { Cloudinary } from "@cloudinary/url-gen"; import { AdvancedImage, lazyload, responsive } from "@cloudinary/react"; // Initialize Cloudinary (client-side only needs cloud name) const cld = new Cloudinary({ cloud: { cloudName: "your-cloud-name" } }); function ImageGallery() { // Your component: reactive asset list from Convex const assets = useQuery(api.cloudinary.listAssets, { limit: 20 }); return (
{assets?.map((asset) => ( ))}
); } ``` **Benefits of this approach:** | This Component | Cloudinary React SDK | | --------------------------------------- | -------------------------------------- | | Stores asset metadata in Convex | Optimized image rendering | | Real-time queries with `useQuery` | Responsive images | | Secure server-side uploads | Lazy loading | | Two upload methods (base64 + direct) | Automatic format selection (WebP/AVIF) | | Asset management (delete, update, list) | Blur placeholders | ## Image Transformations The component provides **fully type-safe** transformation options with IntelliSense support. All transformation parameters are documented with JSDoc comments for easy discovery. ### Basic Usage ```tsx // In React with the transform hook const { transformedUrl } = useCloudinaryImage( api.cloudinary.transform, "my-image-id", { width: 300, height: 200, crop: "fill", gravity: "auto", quality: "auto", format: "auto", } ); // Or with CloudinaryClient in Convex const result = await cloudinary.transform(ctx, "my-image-id", { width: 300, height: 200, crop: "fill", }); ``` ### Transformation Options Reference #### Dimensions | Option | Type | Description | | -------- | -------- | ----------------------- | | `width` | `number` | Output width in pixels | | `height` | `number` | Output height in pixels | #### Crop Modes | Mode | Description | | ------------ | -------------------------------------------------------- | | `"fill"` | Scale and crop to fill exact dimensions (may crop edges) | | `"fit"` | Scale to fit within dimensions (maintains aspect ratio) | | `"crop"` | Extract region (requires gravity or x/y coordinates) | | `"thumb"` | Generate thumbnail using face detection | | `"scale"` | Resize without maintaining aspect ratio (may distort) | | `"pad"` | Resize and pad with background color to exact dimensions | | `"limit"` | Like fit, but only scales down (never enlarges) | | `"fill_pad"` | Fill with smart content-aware padding | | `"auto"` | Automatic cropping based on content | #### Gravity Options | Gravity | Description | | ---------------- | ----------------------------------------- | | `"auto"` | AI-powered detection of important content | | `"face"` | Focus on detected face | | `"faces"` | Focus on multiple faces | | `"auto:subject"` | Focus on main subject | | `"center"` | Center of the image | | `"north"` | Top of the image | | `"south"` | Bottom of the image | | `"east"` | Right side | | `"west"` | Left side | | `"north_east"` | Top-right corner | | `"south_west"` | Bottom-left corner | #### Quality & Format | Option | Values | Description | | --------- | ---------------------------------------------- | -------------------------------------- | | `quality` | `"auto"`, `"auto:best"`, `"auto:eco"`, `1-100` | Compression quality | | `format` | `"auto"`, `"webp"`, `"avif"`, `"jpg"`, `"png"` | Output format | | `dpr` | `1`, `2`, `3`, `"auto"` | Device pixel ratio for retina displays | #### Visual Effects | Option | Examples | Description | | --------- | ------------------------------- [README truncated for prompt length]