I am developing a React Native application using Expo and Supabase. I need to upload images to a Supabase Storage bucket. I am using Expo’s ImagePicker to select images, and I want to upload these images directly to Supabase Storage using the @supabase/supabase-js library.
Despite following various tutorials and examples, the image is not being uploaded to the bucket, and I receive no specific error messages to debug the issue. After selecting the image on my phone and clicking “Choose,” nothing happens—the image does not get uploaded, and no error is displayed.
I have ensured that my Supabase bucket policies allow public uploads and access.
Here is the current code for the upload functionality:
<code>import React, { useState } from 'react';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useNavigation, useRoute } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker';
import { supabase } from '../database/supabaseClient';
import 'react-native-url-polyfill/auto';
import { Upload } from '@supabase/supabase-js';
const UploadSurpriseBagScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const { userId } = route.params || {};
const [name, setName] = useState('');
const [bagNumber, setBagNumber] = useState('');
const [pickupHour, setPickupHour] = useState('');
const [validation, setValidation] = useState('');
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('Breakfast');
const [imageUri, setImageUri] = useState('');
const [uploading, setUploading] = useState(false);
const pickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("You've refused to allow this app to access your photos!");
const pickerResult = await ImagePicker.launchImageLibraryAsync({
if (!pickerResult.cancelled) {
setImageUri(pickerResult.uri);
const uploadImage = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
const fileExt = uri.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `${fileName}`;
const { data, error } = await supabase.storage.from('surprise_bags').upload(filePath, blob);
const { publicURL, error: publicURLError } = await supabase.storage.from('surprise_bags').getPublicUrl(filePath);
console.error('Error uploading image:', error);
alert('Error uploading image');
const handleUploadBag = async () => {
if (!name || !bagNumber || !pickupHour || !validation || !price || !description || !imageUri) {
alert('Please fill out all fields and upload an image');
const imageUrl = await uploadImage(imageUri);
const { data: shop, error: shopError } = await supabase
.eq('employee_id', userId)
console.error('Error fetching shop:', shopError);
alert('Error fetching shop information');
const { error } = await supabase
console.error('Error uploading surprise bag:', error);
alert('Error uploading surprise bag');
alert('Surprise bag uploaded successfully');
<SafeAreaView style={styles.container}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
<Icon name="arrow-left" size={24} color="#000" />
<ScrollView contentContainerStyle={styles.scrollContainer}>
<Text style={styles.headerTitle}>Upload Surprise Bags</Text>
<TouchableOpacity style={styles.uploadPhotoContainer} onPress={async () => {
const response = await pickImage();
setImageUri(response.uri);
<Image source={{ uri: imageUri }} style={styles.uploadedImage} />
<Icon name="image-outline" size={50} color="#82866b" />
<Text style={styles.uploadPhotoText}>Upload Photo</Text>
<Text style={styles.label}>Name</Text>
placeholder="e.g. Surprise Bag"
<Text style={styles.label}>Bag no.</Text>
onChangeText={setBagNumber}
<Text style={styles.label}>Pick up Hour</Text>
placeholder="e.g. 12:30pm - 4:30am"
onChangeText={setPickupHour}
<Text style={styles.label}>Validation</Text>
placeholder="e.g. 07/02/24 - 09/02/24"
onChangeText={setValidation}
<Text style={styles.label}>Price</Text>
<Text style={styles.label}>What you could get</Text>
placeholder="e.g. Lorem ipsum dolor sit amet consectetur."
onChangeText={setDescription}
<Text style={styles.label}>Food Category</Text>
<View style={styles.pickerContainer}>
onPress={() => setCategory('Breakfast')}
style={[styles.pickerButton, category === 'Breakfast' && styles.pickerButtonSelected]}
<Text style={styles.pickerButtonText}>Breakfast</Text>
onPress={() => setCategory('Lunch')}
style={[styles.pickerButton, category === 'Lunch' && styles.pickerButtonSelected]}
<Text style={styles.pickerButtonText}>Lunch</Text>
onPress={() => setCategory('Dinner')}
style={[styles.pickerButton, category === 'Dinner' && styles.pickerButtonSelected]}
<Text style={styles.pickerButtonText}>Dinner</Text>
<TouchableOpacity style={styles.uploadButton} onPress={handleUploadBag} disabled={uploading}>
<Text style={styles.uploadButtonText}>{uploading ? 'Uploading...' : 'Upload Bag'}</Text>
const styles = StyleSheet.create({
backgroundColor: '#FFFFFF',
justifyContent: 'space-between',
backgroundColor: '#82866b',
backgroundColor: '#82866b',
export default UploadSurpriseBagScreen;
<code>import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
Image,
Alert,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useNavigation, useRoute } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker';
import { supabase } from '../database/supabaseClient';
import 'react-native-url-polyfill/auto';
import { Upload } from '@supabase/supabase-js';
const UploadSurpriseBagScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const { userId } = route.params || {};
const [name, setName] = useState('');
const [bagNumber, setBagNumber] = useState('');
const [pickupHour, setPickupHour] = useState('');
const [validation, setValidation] = useState('');
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('Breakfast');
const [imageUri, setImageUri] = useState('');
const [uploading, setUploading] = useState(false);
const pickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("You've refused to allow this app to access your photos!");
return;
}
const pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!pickerResult.cancelled) {
setImageUri(pickerResult.uri);
return pickerResult;
}
};
const uploadImage = async (uri) => {
try {
setUploading(true);
const response = await fetch(uri);
const blob = await response.blob();
const fileExt = uri.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `${fileName}`;
const { data, error } = await supabase.storage.from('surprise_bags').upload(filePath, blob);
if (error) {
throw error;
}
const { publicURL, error: publicURLError } = await supabase.storage.from('surprise_bags').getPublicUrl(filePath);
if (publicURLError) {
throw publicURLError;
}
return publicURL;
} catch (error) {
console.error('Error uploading image:', error);
alert('Error uploading image');
return null;
} finally {
setUploading(false);
}
};
const handleUploadBag = async () => {
if (!name || !bagNumber || !pickupHour || !validation || !price || !description || !imageUri) {
alert('Please fill out all fields and upload an image');
return;
}
const imageUrl = await uploadImage(imageUri);
if (!imageUrl) return;
const { data: shop, error: shopError } = await supabase
.from('shops')
.select('id')
.eq('employee_id', userId)
.single();
if (shopError) {
console.error('Error fetching shop:', shopError);
alert('Error fetching shop information');
return;
}
const { error } = await supabase
.from('surprise_bags')
.insert([
{
employee_id: userId,
shop_id: shop.id,
name,
bag_number: bagNumber,
pickup_hour: pickupHour,
validation,
price,
description,
category,
image_url: imageUrl,
},
]);
if (error) {
console.error('Error uploading surprise bag:', error);
alert('Error uploading surprise bag');
} else {
alert('Surprise bag uploaded successfully');
navigation.goBack();
}
};
return (
<SafeAreaView style={styles.container}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
<Icon name="arrow-left" size={24} color="#000" />
</TouchableOpacity>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<Text style={styles.headerTitle}>Upload Surprise Bags</Text>
<TouchableOpacity style={styles.uploadPhotoContainer} onPress={async () => {
const response = await pickImage();
if (response?.uri) {
setImageUri(response.uri);
}
}}>
{imageUri ? (
<Image source={{ uri: imageUri }} style={styles.uploadedImage} />
) : (
<>
<Icon name="image-outline" size={50} color="#82866b" />
<Text style={styles.uploadPhotoText}>Upload Photo</Text>
</>
)}
</TouchableOpacity>
<Text style={styles.label}>Name</Text>
<TextInput
style={styles.input}
placeholder="e.g. Surprise Bag"
value={name}
onChangeText={setName}
/>
<Text style={styles.label}>Bag no.</Text>
<TextInput
style={styles.input}
placeholder="e.g. #001"
value={bagNumber}
onChangeText={setBagNumber}
/>
<Text style={styles.label}>Pick up Hour</Text>
<TextInput
style={styles.input}
placeholder="e.g. 12:30pm - 4:30am"
value={pickupHour}
onChangeText={setPickupHour}
/>
<Text style={styles.label}>Validation</Text>
<TextInput
style={styles.input}
placeholder="e.g. 07/02/24 - 09/02/24"
value={validation}
onChangeText={setValidation}
/>
<Text style={styles.label}>Price</Text>
<TextInput
style={styles.input}
placeholder="e.g. $3.50"
value={price}
onChangeText={setPrice}
/>
<Text style={styles.label}>What you could get</Text>
<TextInput
style={styles.input}
placeholder="e.g. Lorem ipsum dolor sit amet consectetur."
value={description}
onChangeText={setDescription}
/>
<Text style={styles.label}>Food Category</Text>
<View style={styles.pickerContainer}>
<TouchableOpacity
onPress={() => setCategory('Breakfast')}
style={[styles.pickerButton, category === 'Breakfast' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Breakfast</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Lunch')}
style={[styles.pickerButton, category === 'Lunch' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Lunch</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Dinner')}
style={[styles.pickerButton, category === 'Dinner' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Dinner</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={styles.uploadButton} onPress={handleUploadBag} disabled={uploading}>
<Text style={styles.uploadButtonText}>{uploading ? 'Uploading...' : 'Upload Bag'}</Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
backButton: {
marginTop: 10,
marginLeft: 10,
},
scrollContainer: {
paddingHorizontal: 20,
},
headerTitle: {
fontSize: 26,
fontWeight: 'bold',
textAlign: 'center',
marginTop: 30,
marginBottom: 20,
},
uploadPhotoContainer: {
borderColor: '#676a61',
borderWidth: 1,
borderStyle: 'dashed',
borderRadius: 10,
padding: 20,
alignItems: 'center',
marginBottom: 20,
},
uploadedImage: {
width: 100,
height: 100,
borderRadius: 10,
},
uploadPhotoText: {
fontSize: 16,
color: '#5c5f4c',
textAlign: 'center',
marginVertical: 10,
},
label: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
color: '#5c5f4c',
},
input: {
borderWidth: 1,
borderColor: '#000',
paddingVertical: 8,
paddingHorizontal: 10,
borderRadius: 5,
marginBottom: 20,
height: 50,
},
pickerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
pickerButton: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderWidth: 1,
borderColor: '#000',
borderRadius: 5,
marginHorizontal: 5,
backgroundColor: '#fff',
},
pickerButtonSelected: {
backgroundColor: '#82866b',
},
pickerButtonText: {
color: '#5c5f4c',
},
uploadButton: {
backgroundColor: '#82866b',
paddingVertical: 12,
borderRadius: 5,
alignItems: 'center',
marginBottom: 20,
},
uploadButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
});
export default UploadSurpriseBagScreen;
</code>
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
Image,
Alert,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useNavigation, useRoute } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker';
import { supabase } from '../database/supabaseClient';
import 'react-native-url-polyfill/auto';
import { Upload } from '@supabase/supabase-js';
const UploadSurpriseBagScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const { userId } = route.params || {};
const [name, setName] = useState('');
const [bagNumber, setBagNumber] = useState('');
const [pickupHour, setPickupHour] = useState('');
const [validation, setValidation] = useState('');
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('Breakfast');
const [imageUri, setImageUri] = useState('');
const [uploading, setUploading] = useState(false);
const pickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("You've refused to allow this app to access your photos!");
return;
}
const pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!pickerResult.cancelled) {
setImageUri(pickerResult.uri);
return pickerResult;
}
};
const uploadImage = async (uri) => {
try {
setUploading(true);
const response = await fetch(uri);
const blob = await response.blob();
const fileExt = uri.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `${fileName}`;
const { data, error } = await supabase.storage.from('surprise_bags').upload(filePath, blob);
if (error) {
throw error;
}
const { publicURL, error: publicURLError } = await supabase.storage.from('surprise_bags').getPublicUrl(filePath);
if (publicURLError) {
throw publicURLError;
}
return publicURL;
} catch (error) {
console.error('Error uploading image:', error);
alert('Error uploading image');
return null;
} finally {
setUploading(false);
}
};
const handleUploadBag = async () => {
if (!name || !bagNumber || !pickupHour || !validation || !price || !description || !imageUri) {
alert('Please fill out all fields and upload an image');
return;
}
const imageUrl = await uploadImage(imageUri);
if (!imageUrl) return;
const { data: shop, error: shopError } = await supabase
.from('shops')
.select('id')
.eq('employee_id', userId)
.single();
if (shopError) {
console.error('Error fetching shop:', shopError);
alert('Error fetching shop information');
return;
}
const { error } = await supabase
.from('surprise_bags')
.insert([
{
employee_id: userId,
shop_id: shop.id,
name,
bag_number: bagNumber,
pickup_hour: pickupHour,
validation,
price,
description,
category,
image_url: imageUrl,
},
]);
if (error) {
console.error('Error uploading surprise bag:', error);
alert('Error uploading surprise bag');
} else {
alert('Surprise bag uploaded successfully');
navigation.goBack();
}
};
return (
<SafeAreaView style={styles.container}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
<Icon name="arrow-left" size={24} color="#000" />
</TouchableOpacity>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<Text style={styles.headerTitle}>Upload Surprise Bags</Text>
<TouchableOpacity style={styles.uploadPhotoContainer} onPress={async () => {
const response = await pickImage();
if (response?.uri) {
setImageUri(response.uri);
}
}}>
{imageUri ? (
<Image source={{ uri: imageUri }} style={styles.uploadedImage} />
) : (
<>
<Icon name="image-outline" size={50} color="#82866b" />
<Text style={styles.uploadPhotoText}>Upload Photo</Text>
</>
)}
</TouchableOpacity>
<Text style={styles.label}>Name</Text>
<TextInput
style={styles.input}
placeholder="e.g. Surprise Bag"
value={name}
onChangeText={setName}
/>
<Text style={styles.label}>Bag no.</Text>
<TextInput
style={styles.input}
placeholder="e.g. #001"
value={bagNumber}
onChangeText={setBagNumber}
/>
<Text style={styles.label}>Pick up Hour</Text>
<TextInput
style={styles.input}
placeholder="e.g. 12:30pm - 4:30am"
value={pickupHour}
onChangeText={setPickupHour}
/>
<Text style={styles.label}>Validation</Text>
<TextInput
style={styles.input}
placeholder="e.g. 07/02/24 - 09/02/24"
value={validation}
onChangeText={setValidation}
/>
<Text style={styles.label}>Price</Text>
<TextInput
style={styles.input}
placeholder="e.g. $3.50"
value={price}
onChangeText={setPrice}
/>
<Text style={styles.label}>What you could get</Text>
<TextInput
style={styles.input}
placeholder="e.g. Lorem ipsum dolor sit amet consectetur."
value={description}
onChangeText={setDescription}
/>
<Text style={styles.label}>Food Category</Text>
<View style={styles.pickerContainer}>
<TouchableOpacity
onPress={() => setCategory('Breakfast')}
style={[styles.pickerButton, category === 'Breakfast' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Breakfast</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Lunch')}
style={[styles.pickerButton, category === 'Lunch' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Lunch</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Dinner')}
style={[styles.pickerButton, category === 'Dinner' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Dinner</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={styles.uploadButton} onPress={handleUploadBag} disabled={uploading}>
<Text style={styles.uploadButtonText}>{uploading ? 'Uploading...' : 'Upload Bag'}</Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
backButton: {
marginTop: 10,
marginLeft: 10,
},
scrollContainer: {
paddingHorizontal: 20,
},
headerTitle: {
fontSize: 26,
fontWeight: 'bold',
textAlign: 'center',
marginTop: 30,
marginBottom: 20,
},
uploadPhotoContainer: {
borderColor: '#676a61',
borderWidth: 1,
borderStyle: 'dashed',
borderRadius: 10,
padding: 20,
alignItems: 'center',
marginBottom: 20,
},
uploadedImage: {
width: 100,
height: 100,
borderRadius: 10,
},
uploadPhotoText: {
fontSize: 16,
color: '#5c5f4c',
textAlign: 'center',
marginVertical: 10,
},
label: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
color: '#5c5f4c',
},
input: {
borderWidth: 1,
borderColor: '#000',
paddingVertical: 8,
paddingHorizontal: 10,
borderRadius: 5,
marginBottom: 20,
height: 50,
},
pickerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
pickerButton: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderWidth: 1,
borderColor: '#000',
borderRadius: 5,
marginHorizontal: 5,
backgroundColor: '#fff',
},
pickerButtonSelected: {
backgroundColor: '#82866b',
},
pickerButtonText: {
color: '#5c5f4c',
},
uploadButton: {
backgroundColor: '#82866b',
paddingVertical: 12,
borderRadius: 5,
alignItems: 'center',
marginBottom: 20,
},
uploadButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
});
export default UploadSurpriseBagScreen;
And i used the following policies:
<code>CREATE POLICY "Anyone can upload to surprise_bags"
WITH CHECK (bucket_id = 'surprise_bags');
<code>CREATE POLICY "Anyone can upload to surprise_bags"
ON storage.objects
FOR INSERT
WITH CHECK (bucket_id = 'surprise_bags');
</code>
CREATE POLICY "Anyone can upload to surprise_bags"
ON storage.objects
FOR INSERT
WITH CHECK (bucket_id = 'surprise_bags');
<code>CREATE POLICY "Anyone can update surprise_bags"
USING (bucket_id = 'surprise_bags')
WITH CHECK (bucket_id = 'surprise_bags');
<code>CREATE POLICY "Anyone can update surprise_bags"
ON storage.objects
FOR UPDATE
USING (bucket_id = 'surprise_bags')
WITH CHECK (bucket_id = 'surprise_bags');
</code>
CREATE POLICY "Anyone can update surprise_bags"
ON storage.objects
FOR UPDATE
USING (bucket_id = 'surprise_bags')
WITH CHECK (bucket_id = 'surprise_bags');
<code>CREATE POLICY "Anyone can view surprise_bags"
USING (bucket_id = 'surprise_bags');
<code>CREATE POLICY "Anyone can view surprise_bags"
ON storage.objects
FOR SELECT
USING (bucket_id = 'surprise_bags');
</code>
CREATE POLICY "Anyone can view surprise_bags"
ON storage.objects
FOR SELECT
USING (bucket_id = 'surprise_bags');