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:
- Go to Settings → Upload → Upload Presets
- Click Add Upload Preset
- Set:
-
Signing mode:
Unsigned
-
Folder:
mynexthome
(or whatever fits your project)
-
Signing mode:
- 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>
)
}
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')
Then fetch()
sends this to the Cloudinary endpoint:
https://api.cloudinary.com/v1_1/<your-cloud-name>/image/upload
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 }
)
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
💨 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
-
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)