Skip to main content

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 library
const { 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 asset
const 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 library
const 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 library
await MediaLibrary.saveToLibraryAsync('file:///path/to/image.jpg');
deleteAssetsAsync(assets)
(assets: Asset[] | string[]) => Promise<boolean>
Deletes assets from the library
const deleted = await MediaLibrary.deleteAssetsAsync([asset1, asset2]);
getAlbumsAsync(options)
(options?: AlbumQueryOptions) => Promise<Album[]>
Gets list of albums
const albums = await MediaLibrary.getAlbumsAsync();
getAlbumAsync(title)
(title: string) => Promise<Album | null>
Gets album by title
const album = await MediaLibrary.getAlbumAsync('MyAlbum');
createAlbumAsync(albumName, asset, copyAsset)
(albumName: string, asset?: Asset | string, copyAsset?: boolean) => Promise<Album>
Creates a new album
const album = await MediaLibrary.createAlbumAsync('Vacation', asset);
addAssetsToAlbumAsync(assets, album, copyAssets)
(assets: Asset[] | string[], album: Album | string, copyAssets?: boolean) => Promise<boolean>
Adds assets to an album
await MediaLibrary.addAssetsToAlbumAsync([asset1, asset2], album);
removeAssetsFromAlbumAsync(assets, album)
(assets: Asset[] | string[], album: Album | string) => Promise<boolean>
Removes assets from an album
await MediaLibrary.removeAssetsFromAlbumAsync([asset], album);
deleteAlbumsAsync(albums)
(albums: Album[] | string[]) => Promise<boolean>
Deletes albums
await MediaLibrary.deleteAlbumsAsync([album]);
requestPermissionsAsync(writeOnly)
(writeOnly?: boolean) => Promise<PermissionResponse>
Requests media library permissions
const { 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 permissions
const [permission, requestPermission] = MediaLibrary.usePermissions();

Types

Asset

id
string
Asset identifier
filename
string
File name
uri
string
Asset URI
mediaType
MediaType
Type: 'photo', 'video', 'audio', 'unknown'
width
number
Width in pixels
height
number
Height in pixels
creationTime
number
Creation timestamp
modificationTime
number
Modification timestamp
duration
number
Duration for videos in seconds

AssetInfo

Extends Asset with additional fields:
localUri
string
Local file URI
location
Location
GPS location data
exif
object
EXIF metadata
isFavorite
boolean
Whether asset is marked as favorite

Album

id
string
Album identifier
title
string
Album name
assetCount
number
Number of assets in album
type
string
Album type

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);
  }
}
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 Support

PlatformSupported
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

  1. Pagination: Use first and after for efficient loading
  2. Permission Check: Always check permissions before operations
  3. Asset Caching: Cache asset URIs to avoid repeated queries
  4. Delete Carefully: Confirm before deleting user photos
  5. Album Organization: Group related photos in albums

Resources