A native Expo module for previewing files using QuickLook on iOS and system viewers on Android.
- Native rendering, not a WebView — Uses iOS QuickLook and Android system viewers. Supports 100+ file formats out of the box with instant rendering, built-in search, and print support.
- Built for Expo — Zero config, no native linking. Just
npx expo installand go. Full TypeScript API with async/await. Works on both New and Old Architecture. - More than just preview — Remote file downloads with auth headers, iOS editing/markup, multi-file swipe navigation, thumbnail generation, and lifecycle events (
onDismiss,onEditedFile,onSavedEditedCopy). Most alternatives only open a local file in read-only mode.
| iOS | Android |
|---|---|
demo-ios.mp4 |
demo-android.mp4 |
npx expo install @magrinj/expo-quick-lookOpens a native file preview.
- iOS: Presents
QLPreviewControllermodally. Promise resolves when the user dismisses the preview. - Android: Launches an Intent chooser with
ACTION_VIEW. Promise resolves immediately after launch.
import ExpoQuickLook from '@magrinj/expo-quick-look';
await ExpoQuickLook.previewFile({
uri: '/path/to/file.pdf',
// Android only
chooserTitle: 'Open with',
// iOS only - 'disabled' | 'createCopy' | 'updateContents'
editingMode: 'createCopy',
});Remote files are also supported — the library downloads the file automatically:
await ExpoQuickLook.previewFile({
uri: 'https://example.com/document.pdf',
});Authenticated downloads — pass custom headers for protected endpoints:
await ExpoQuickLook.previewFile({
uri: 'https://api.example.com/documents/123/download',
requestOptions: {
headers: {
Authorization: `Bearer ${token}`,
},
},
});Opens a multi-file preview with swipe navigation.
await ExpoQuickLook.previewFiles({
uris: ['/path/to/file1.pdf', '/path/to/file2.png'],
initialIndex: 0,
editingMode: 'disabled',
});Checks whether a file can be previewed.
- iOS: Uses
QLPreviewController.canPreview. - Android: Checks if any installed app can handle the file's MIME type.
const supported = await ExpoQuickLook.canPreview('/path/to/file.pdf');
// or a remote URL
const supported = await ExpoQuickLook.canPreview('https://example.com/doc.pdf');For remote URLs, this performs a best-effort check based on the file extension — no download occurs.
Generates a thumbnail for a file.
const thumbnail = await ExpoQuickLook.generateThumbnail({
uri: '/path/to/file.pdf',
size: { width: 200, height: 200 },
scale: 2, // defaults to device scale
});
// thumbnail.uri - file:// URI to the generated PNG
// thumbnail.width / thumbnail.height - pixel dimensionsNote:
generateThumbnailonly supports local files. Pass a remote URL and it will throw an error.
type RequestOptions = {
headers?: Record<string, string>;
};
type PreviewFileOptions = {
uri: string;
requestOptions?: RequestOptions;
chooserTitle?: string; // Android only
editingMode?: EditingMode; // iOS only
};
type PreviewFilesOptions = {
uris: string[];
requestOptions?: RequestOptions;
initialIndex?: number;
editingMode?: EditingMode;
};
type EditingMode = 'disabled' | 'createCopy' | 'updateContents';
type ThumbnailOptions = {
uri: string;
size: { width: number; height: number };
scale?: number;
};
type ThumbnailResult = {
uri: string;
width: number;
height: number;
};Subscribe using addListener or the useEvent hook from Expo.
| Event | Platform | Payload |
|---|---|---|
onDismiss |
iOS | {} |
onEditedFile |
iOS | { filePath: string } |
onSavedEditedCopy |
iOS | { originalPath: string, editedPath: string } |
const subscription = ExpoQuickLook.addListener('onDismiss', () => {
console.log('Preview dismissed');
});
// Clean up
subscription.remove();| Behavior | iOS | Android |
|---|---|---|
| Preview style | In-app modal (QuickLook) | External app (Intent chooser) |
| Promise resolution | On dismiss | Immediately after launch |
| Multi-file preview | Supported | Not supported |
| Editing/markup | Supported | Not supported |
| Thumbnails | Supported | Not supported |
| Events | All events | None |
| Remote URL support | Download to temp + preview | Download to cache + launch |
See the example app for a complete demo.
If you find this library useful, consider supporting its development:
Made with ❤️ by Jérémy Magrin
If you find this useful, please star it ⭐ — it helps a lot!