DEV Community

Cathy Lai
Cathy Lai

Posted on

Uploading Photos from Your React Native App to Cloudinary (with Expo)

If you’re building a mobile app that lets users upload photos — maybe property listings, profile images, or food pics — Cloudinary is one of the simplest and most powerful image-hosting options available.

In this short tutorial, we’ll use the Cloudinary API directly with Expo’s fetch() and FormData to upload an image and get back a CDN URL that’s instantly optimized and ready to display.


☁️ Why Cloudinary?

Cloudinary offers:

  • Free tier: generous monthly bandwidth and storage
  • Automatic optimization: f_auto,q_auto delivers the best format and compression for each device
  • Instant CDN delivery: your image is globally cached and fast to load
  • No backend required: you can upload directly from your mobile app with an unsigned preset

🪄 Step 1. Create an unsigned upload preset

In your Cloudinary dashboard:

  1. Go to Settings → Upload → Upload Presets
  2. Click Add Upload Preset
  3. Set:
    • Signing mode: Unsigned
    • Folder: mynexthome (or whatever fits your project)
  4. Save, and note your Preset name (e.g., uc9d4rhd)

You’ll also see your Cloud name at the top of your dashboard — we’ll need that soon.


⚙️ Step 2. Create a simple test upload in Expo

Let’s create a new dev screen or add a button to any screen in your Expo app.

// CloudinaryTest.tsx
import React from 'react'
import { View, Text, Button, Alert } from 'react-native'

export default function CloudinaryTest() {
  const testUpload = async () => {
    try {
      const data = new FormData()
      // You can use any test image URL here
      data.append(
        'file',
        'https://upload.wikimedia.org/wikipedia/commons/a/a3/June_odd-eyed-cat_cropped.jpg'
      )
      // 👇 your unsigned preset name
      data.append('upload_preset', 'uc9d4rhd')
      // optional: specify a folder to keep uploads organized
      data.append('folder', 'mynexthome')

      const res = await fetch(
        'https://api.cloudinary.com/v1_1/radiantpathstudio/image/upload',
        {
          method: 'POST',
          body: data,
        }
      )

      const json = await res.json()
      console.log('✅ Upload result:', json)
      Alert.alert('Uploaded!', json.secure_url)
    } catch (err: any) {
      console.error('❌ Upload failed:', err)
      Alert.alert('Error', err.message)
    }
  }

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 18, marginBottom: 10 }}>Cloudinary Upload Test</Text>
      <Button title="Run Test Upload" onPress={testUpload} />
    </View>
  )
}
Enter fullscreen mode Exit fullscreen mode

That’s it! Press the button, and you should see a success alert and a new image appear inside your Cloudinary Media Library (in the mynexthome folder).


🧩 How it works

FormData() in JavaScript acts like a virtual HTML form.

Each .append() adds a field to be sent to the Cloudinary API:

data.append('file', <image or URL>)
data.append('upload_preset', 'your_unsigned_preset')
data.append('folder', 'your_target_folder')
Enter fullscreen mode Exit fullscreen mode

Then fetch() sends this to the Cloudinary endpoint:

https://api.cloudinary.com/v1_1/<your-cloud-name>/image/upload
Enter fullscreen mode Exit fullscreen mode

Cloudinary responds with JSON that includes your file’s secure_url, public_id, width, height, and more.


📱 Considerations for iPhone photo sizes

Modern iPhones capture very high-resolution photos, which can be quite large:

Model Format Typical size
iPhone 12–14 HEIC 1.5–3 MB
iPhone 15 (24 MP) HEIC 2–5 MB
JPEG conversions JPEG 4–8 MB

These are too large for fast uploads — and users don’t need full-camera resolution inside an app.

Before uploading, it’s smart to resize and compress the image:

import * as ImageManipulator from 'expo-image-manipulator'

const compressed = await ImageManipulator.manipulateAsync(
  localUri,
  [{ resize: { width: 1600 } }], // 1600px wide is plenty for screens
  { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG }
)
Enter fullscreen mode Exit fullscreen mode

After this step, your photos will be around 0.8 MB–1.2 MB each — perfect for Cloudinary’s free tier and fast mobile upload speeds.

Note: the IamgeManipulator is a native module and requires

npx expo prebuild 
// then
npx expo run:ios or npx expo run:android
Enter fullscreen mode Exit fullscreen mode

💨 Optional: Auto-optimized display URLs

Cloudinary automatically serves optimized versions of your photos.

You can adjust size and quality right from the URL — no re-uploading required:

https://res.cloudinary.com/radiantpathstudio/image/upload/f_auto,q_auto,w_1200/mynexthome/your_photo.jpg
Enter fullscreen mode Exit fullscreen mode
  • f_auto → automatically use WebP or AVIF for compatible devices
  • q_auto → smart quality optimization
  • w_1200 → resize width to 1200 px

🧭 Summary

  • Set up an unsigned preset in Cloudinary
  • POST a file or URL to /image/upload with your preset name
  • Receive a CDN URL instantly optimized for all devices
  • Resize before upload to keep iPhone photos lightweight

Cloudinary handles the hosting, resizing, and caching — you focus on your app.


Next step:

You can extend this demo to use expo-image-picker for user-selected images, or pair it with your Supabase user auth to associate each upload with a user ID.

Happy building! 🌤️

Top comments (0)