Expo Integration
This guide explains how to integrate FileZen with Expo/React Native applications. We recommend using our dedicated SDK packages for the best experience.
📁 Code Example: View the complete working example at github.com/FileZen/filezen/tree/main/apps/expo-app
Installation
Install the required FileZen packages for an Expo project:
Setup
1. Environment Configuration
Set your FileZen API key as an environment variable. This can be done in your local .env file during development or as a system environment variable in production.
FILEZEN_API_KEY=your_api_key_hereThe FileZen SDK automatically detects the FILEZEN_API_KEY environment variable for server-side operations, so no additional configuration is needed in your API routes.
2. Provider Setup
Choose between two providers based on your needs:
Option A: ZenStorageProvider (Direct Upload)
For direct uploads to FileZen without a server:
import { ZenStorageProvider } from '@filezen/react';
export default function RootLayout() {
return (
<ZenStorageProvider
apiKey="your-filezen-api-key" // Optional: can use FILEZEN_API_KEY env variable
>
{/* Your app components */}
</ZenStorageProvider>
);
}Option B: ZenClientProvider (Server Integration)
For server-side presigned URL generation:
import { ZenClientProvider } from '@filezen/react';
export default function RootLayout() {
return (
<ZenClientProvider
signUrl="https://your-server.com/api/filezen/sign" // Your server endpoint
>
{/* Your app components */}
</ZenClientProvider>
);
}Provider Comparison: ZenStorageProvider is simpler for direct uploads, while ZenClientProvider requires a server but keeps your API key secure on the backend.
Using FileZen in Expo
Basic File Upload Component
Create a component that handles file uploads using Expo’s file pickers:
import { useFileZen } from '@filezen/react';
import * as DocumentPicker from 'expo-document-picker';
import * as ImagePicker from 'expo-image-picker';
import React, { useState } from 'react';
import { Alert, Text, TouchableOpacity, View } from 'react-native';
export default function FileUpload() {
const { upload } = useFileZen();
const [uploading, setUploading] = useState(false);
const pickAndUploadImage = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: false,
quality: 1,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
setUploading(true);
await upload(asset.uri, {
name: asset.fileName || `image_${Date.now()}.jpg`,
folder: 'mobile-uploads',
projectId: 'your-project-id',
listener: {
onComplete: (upload) => {
setUploading(false);
Alert.alert('Success', 'Upload complete!');
},
onError: (upload, error) => {
setUploading(false);
Alert.alert('Error', error.message);
},
},
});
}
} catch (error) {
Alert.alert('Error', 'Failed to pick image');
}
};
const pickAndUploadDocument = async () => {
try {
const result = await DocumentPicker.getDocumentAsync({
type: '*/*',
copyToCacheDirectory: true,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
setUploading(true);
await upload(asset.uri, {
name: asset.name,
folder: 'mobile-uploads',
projectId: 'your-project-id',
listener: {
onComplete: (upload) => {
setUploading(false);
Alert.alert('Success', 'Upload complete!');
},
onError: (upload, error) => {
setUploading(false);
Alert.alert('Error', error.message);
},
},
});
}
} catch (error) {
Alert.alert('Error', 'Failed to pick document');
}
};
return (
<View>
<TouchableOpacity onPress={pickAndUploadImage} disabled={uploading}>
<Text>📷 Pick & Upload Image</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pickAndUploadDocument} disabled={uploading}>
<Text>📄 Pick & Upload Document</Text>
</TouchableOpacity>
{uploading && <Text>Uploading...</Text>}
</View>
);
}Using ZenClientProvider (Server Integration)
If you’re using ZenClientProvider for server-side integration:
import { useZenClient } from '@filezen/react';
import * as ImagePicker from 'expo-image-picker';
import React, { useState } from 'react';
import { Alert, Text, TouchableOpacity, View } from 'react-native';
export default function FileUpload() {
const { upload } = useZenClient();
const [uploading, setUploading] = useState(false);
const pickAndUploadImage = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: false,
quality: 1,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
setUploading(true);
await upload(asset.uri, {
name: asset.fileName || `image_${Date.now()}.jpg`,
folder: 'mobile-uploads',
projectId: 'your-project-id',
listener: {
onComplete: (upload) => {
setUploading(false);
Alert.alert('Success', 'Upload complete!');
},
onError: (upload, error) => {
setUploading(false);
Alert.alert('Error', error.message);
},
},
});
}
} catch (error) {
Alert.alert('Error', 'Failed to pick image');
}
};
return (
<View>
<TouchableOpacity onPress={pickAndUploadImage} disabled={uploading}>
<Text>📷 Pick & Upload Image</Text>
</TouchableOpacity>
{uploading && <Text>Uploading...</Text>}
</View>
);
}Advanced Upload with Progress Tracking
import { useFileZen } from '@filezen/react';
import * as ImagePicker from 'expo-image-picker';
import React, { useState } from 'react';
import { Alert, Text, TouchableOpacity, View } from 'react-native';
export default function AdvancedUpload() {
const { upload } = useFileZen();
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const uploadWithProgress = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: false,
quality: 1,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
setUploading(true);
setProgress(0);
await upload(asset.uri, {
name: asset.fileName || `image_${Date.now()}.jpg`,
folder: 'mobile-uploads',
metadata: {
userId: '12345',
uploadSource: 'mobile-app'
},
listener: {
onStart: (upload) => {
console.log('Upload started');
},
onProgress: (upload, progress) => {
setProgress(progress.percent || 0);
console.log(`Progress: ${progress.percent}%`);
},
onComplete: (upload) => {
setUploading(false);
setProgress(100);
Alert.alert('Success', `Upload complete! URL: ${upload.file?.url}`);
},
onError: (upload, error) => {
setUploading(false);
setProgress(0);
Alert.alert('Error', `Upload failed: ${error.message}`);
},
},
});
}
} catch (error) {
Alert.alert('Error', 'Failed to pick image');
}
};
return (
<View>
<TouchableOpacity onPress={uploadWithProgress} disabled={uploading}>
<Text>📷 Upload with Progress</Text>
</TouchableOpacity>
{uploading && (
<View>
<Text>Uploading... {progress.toFixed(0)}%</Text>
</View>
)}
</View>
);
}Configuration Options
Provider Comparison
| Feature | ZenStorageProvider | ZenClientProvider |
|---|---|---|
| Upload Method | Direct to FileZen | Direct to FileZen |
| API Key | Required (client-side) | Server-side only |
| Server Required | No | Yes (for generating signed URLs) |
| Use Case | Simple apps, prototypes | Production apps with custom logic |
Image Resizing (Built-in)
FileZen automatically resizes images on-the-fly using URL parameters:
// Original image
<Image source={{ uri: upload.file?.url }} />
// Resized thumbnail (120x120, cover fit)
<Image source={{
uri: `${upload.file?.url}?width=120&height=120&fit=cover`
}} />For all available resizing parameters, see the Dynamic Image Documentation .
The
upload()method uploads files directly to FileZen from the client side. This reduces traffic usage on your server since files don’t pass through your backend.