# 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 <your_cloud_name>
npx convex env set CLOUDINARY_API_KEY <your_api_key>
npx convex env set CLOUDINARY_API_SECRET <your_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 (
    <div>
      {isUploading && <p>Uploading... {progress}%</p>}
      {images?.map((img) => (
      ))}
    </div>
  );
}
```

### 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<HTMLInputElement>) => {
    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 (
    <div>
      <input type="file" onChange={handleFileChange} disabled={isUploading} />
      {isUploading && <p>Uploading... {progress}%</p>}
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}
```

### 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 <div>Loading...</div>;
  return <img src={transformedUrl} alt="" />;
}
```

### 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 (
    <div>
      <button onClick={handleUpdateTags}>Add Tags</button>
      <button onClick={handleDelete}>Delete</button>
    </div>
  );
}
```

## 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 (
    <div className="gallery">
      {assets?.map((asset) => (
        <AdvancedImage
          key={asset.publicId}
          cldImg={cld.image(asset.publicId)}
          plugins={[lazyload(), responsive()]}
          // Cloudinary SDK handles:
          // - Responsive sizing
          // - Lazy loading
          // - Automatic format (WebP/AVIF)
          // - Retina displays
        />
      ))}
    </div>
  );
}
```

**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]