expo-media-library
Version: 55.0.6
Provides access to the user’s media library, allowing you to read, create, and manage photos and videos on the device. Works with the camera roll on iOS and the media store on Android.
Installation
npx expo install expo-media-library
Usage
import * as MediaLibrary from 'expo-media-library';
import { Button, View, FlatList, Image } from 'react-native';
import { useState, useEffect } from 'react';
export default function App() {
const [photos, setPhotos] = useState<MediaLibrary.Asset[]>([]);
const [permission, requestPermission] = MediaLibrary.usePermissions();
useEffect(() => {
loadPhotos();
}, []);
const loadPhotos = async () => {
if (permission?.granted) {
const { assets } = await MediaLibrary.getAssetsAsync({
first: 20,
mediaType: 'photo',
});
setPhotos(assets);
}
};
return (
<View>
{!permission?.granted && (
<Button title="Request Permission" onPress={requestPermission} />
)}
<FlatList
data={photos}
renderItem={({ item }) => (
<Image source={{ uri: item.uri }} style={{ width: 100, height: 100 }} />
)}
/>
</View>
);
}
API Reference
Methods
getAssetsAsync(options)
(options?: AssetQueryOptions) => Promise<PagedInfo>
Retrieves assets from the media libraryconst { assets, endCursor, hasNextPage } = await MediaLibrary.getAssetsAsync({
first: 20,
mediaType: 'photo',
sortBy: 'creationTime',
});
getAssetInfoAsync(asset)
(asset: Asset | string) => Promise<AssetInfo>
Gets detailed information about a specific assetconst info = await MediaLibrary.getAssetInfoAsync(asset);
console.log('Location:', info.location);
console.log('EXIF:', info.exif);
createAssetAsync(localUri)
(localUri: string) => Promise<Asset>
Saves a file to the media libraryconst asset = await MediaLibrary.createAssetAsync('file:///path/to/photo.jpg');
console.log('Saved asset:', asset.id);
saveToLibraryAsync(localUri)
(localUri: string) => Promise<void>
Saves an image or video to the libraryawait MediaLibrary.saveToLibraryAsync('file:///path/to/image.jpg');
deleteAssetsAsync(assets)
(assets: Asset[] | string[]) => Promise<boolean>
Deletes assets from the libraryconst deleted = await MediaLibrary.deleteAssetsAsync([asset1, asset2]);
getAlbumsAsync(options)
(options?: AlbumQueryOptions) => Promise<Album[]>
Gets list of albumsconst albums = await MediaLibrary.getAlbumsAsync();
getAlbumAsync(title)
(title: string) => Promise<Album | null>
Gets album by titleconst album = await MediaLibrary.getAlbumAsync('MyAlbum');
createAlbumAsync(albumName, asset, copyAsset)
(albumName: string, asset?: Asset | string, copyAsset?: boolean) => Promise<Album>
Creates a new albumconst album = await MediaLibrary.createAlbumAsync('Vacation', asset);
addAssetsToAlbumAsync(assets, album, copyAssets)
(assets: Asset[] | string[], album: Album | string, copyAssets?: boolean) => Promise<boolean>
Adds assets to an albumawait MediaLibrary.addAssetsToAlbumAsync([asset1, asset2], album);
removeAssetsFromAlbumAsync(assets, album)
(assets: Asset[] | string[], album: Album | string) => Promise<boolean>
Removes assets from an albumawait MediaLibrary.removeAssetsFromAlbumAsync([asset], album);
deleteAlbumsAsync(albums)
(albums: Album[] | string[]) => Promise<boolean>
Deletes albumsawait MediaLibrary.deleteAlbumsAsync([album]);
requestPermissionsAsync(writeOnly)
(writeOnly?: boolean) => Promise<PermissionResponse>
Requests media library permissionsconst { status } = await MediaLibrary.requestPermissionsAsync();
getPermissionsAsync(writeOnly)
(writeOnly?: boolean) => Promise<PermissionResponse>
Checks current permission status
Hooks
usePermissions(options)
(options?: { writeOnly?: boolean }) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>]
Hook for managing permissionsconst [permission, requestPermission] = MediaLibrary.usePermissions();
Types
Asset
Type: 'photo', 'video', 'audio', 'unknown'
Duration for videos in seconds
AssetInfo
Extends Asset with additional fields:
Whether asset is marked as favorite
Album
Number of assets in album
Examples
Load Recent Photos
import * as MediaLibrary from 'expo-media-library';
async function loadRecentPhotos() {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
alert('Permission required');
return;
}
const { assets } = await MediaLibrary.getAssetsAsync({
first: 50,
mediaType: 'photo',
sortBy: 'creationTime',
});
console.log(`Found ${assets.length} photos`);
assets.forEach(asset => {
console.log(asset.filename, asset.uri);
});
}
Paginated Asset Loading
import * as MediaLibrary from 'expo-media-library';
async function loadAllPhotos() {
let allAssets: MediaLibrary.Asset[] = [];
let endCursor: string | undefined;
while (true) {
const { assets, hasNextPage, endCursor: cursor } =
await MediaLibrary.getAssetsAsync({
first: 100,
after: endCursor,
mediaType: 'photo',
});
allAssets = [...allAssets, ...assets];
if (!hasNextPage) break;
endCursor = cursor;
}
console.log(`Loaded ${allAssets.length} photos`);
}
Save Image to Library
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
async function saveImage(imageUri: string) {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
alert('Permission required');
return;
}
try {
const asset = await MediaLibrary.createAssetAsync(imageUri);
console.log('Image saved:', asset.id);
// Optionally add to album
const album = await MediaLibrary.getAlbumAsync('MyApp Photos');
if (album) {
await MediaLibrary.addAssetsToAlbumAsync([asset], album);
} else {
await MediaLibrary.createAlbumAsync('MyApp Photos', asset);
}
} catch (error) {
console.error('Error saving image:', error);
}
}
Get Asset Details
import * as MediaLibrary from 'expo-media-library';
async function getPhotoDetails(asset: MediaLibrary.Asset) {
const info = await MediaLibrary.getAssetInfoAsync(asset);
console.log('Local URI:', info.localUri);
console.log('File size:', info.exif?.FileSize);
if (info.location) {
console.log('GPS:', info.location.latitude, info.location.longitude);
}
if (info.exif) {
console.log('Camera:', info.exif.Make, info.exif.Model);
console.log('Date taken:', info.exif.DateTime);
}
}
Manage Albums
import * as MediaLibrary from 'expo-media-library';
async function createPhotoAlbum(albumName: string, photos: string[]) {
// Create first asset
const firstAsset = await MediaLibrary.createAssetAsync(photos[0]);
// Create album with first asset
const album = await MediaLibrary.createAlbumAsync(albumName, firstAsset);
// Add remaining photos
if (photos.length > 1) {
const remainingAssets = await Promise.all(
photos.slice(1).map(uri => MediaLibrary.createAssetAsync(uri))
);
await MediaLibrary.addAssetsToAlbumAsync(remainingAssets, album);
}
console.log(`Created album "${albumName}" with ${photos.length} photos`);
}
Delete Assets
import * as MediaLibrary from 'expo-media-library';
async function deletePhotos(assets: MediaLibrary.Asset[]) {
const confirmed = confirm(`Delete ${assets.length} photos?`);
if (!confirmed) return;
try {
const success = await MediaLibrary.deleteAssetsAsync(assets);
if (success) {
console.log('Photos deleted successfully');
}
} catch (error) {
console.error('Error deleting photos:', error);
}
}
Complete Photo Gallery Example
import * as MediaLibrary from 'expo-media-library';
import { useState, useEffect } from 'react';
import { View, FlatList, Image, StyleSheet, Button, Text } from 'react-native';
export default function PhotoGallery() {
const [photos, setPhotos] = useState<MediaLibrary.Asset[]>([]);
const [hasNextPage, setHasNextPage] = useState(true);
const [endCursor, setEndCursor] = useState<string>();
const [permission, requestPermission] = MediaLibrary.usePermissions();
useEffect(() => {
if (permission?.granted) {
loadPhotos();
}
}, [permission]);
const loadPhotos = async () => {
const result = await MediaLibrary.getAssetsAsync({
first: 20,
after: endCursor,
mediaType: 'photo',
sortBy: 'creationTime',
});
setPhotos(prev => [...prev, ...result.assets]);
setHasNextPage(result.hasNextPage);
setEndCursor(result.endCursor);
};
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text>Media library permission required</Text>
<Button title="Grant Permission" onPress={requestPermission} />
</View>
);
}
return (
<FlatList
data={photos}
numColumns={3}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Image source={{ uri: item.uri }} style={styles.photo} />
)}
onEndReached={() => {
if (hasNextPage) loadPhotos();
}}
/>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 10 },
photo: { width: 120, height: 120, margin: 2 },
});
| Platform | Supported |
|---|
| iOS | ✅ |
| Android | ✅ |
| Web | ❌ |
Permissions
Media library permissions are required to access photos and videos.
iOS
Add to app.json:
{
"expo": {
"plugins": [
[
"expo-media-library",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
"savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos."
}
]
]
}
}
Android
Permissions automatically added:
READ_EXTERNAL_STORAGE / READ_MEDIA_IMAGES / READ_MEDIA_VIDEO (for reading)
WRITE_EXTERNAL_STORAGE (for writing on Android < 10)
ACCESS_MEDIA_LOCATION (for location data)
Best Practices
- Pagination: Use
first and after for efficient loading
- Permission Check: Always check permissions before operations
- Asset Caching: Cache asset URIs to avoid repeated queries
- Delete Carefully: Confirm before deleting user photos
- Album Organization: Group related photos in albums
Resources