A cross-platform, high-performance PDF viewer for React Native and Expo, built on top of native* PDF rendering engines.
| iOS | Android |
|---|---|
![]() |
![]() |
- Supports Android and iOS
- Uses Apple's
PDFKiton iOS - *Uses kishannareshpal/AndroidPdfViewer on Android which is a maintained
fork of barteksc/AndroidPdfViewerV2 which uses the open-source PDFium PDF rendering engine.
- Note: We'll be looking to switch to
androidx.pdfon Android once that becomes stable.
- Note: We'll be looking to switch to
- Uses Apple's
- Load PDFs from local file paths
- Supports local file URIs on android and iOS and ContentResolver URIs on android
- Pinch-to-zoom / double-tap-to-zoom and drag gestures
- Support for password-protected PDFs
- Horizontal and vertical reading modes
- Support for content-insets
- Color inversion
This package works with both Expo and framework-less React Native projects but Expo provides a more streamlined experience.
npx expo install @kishannareshpal/expo-pdf-
Install this package
npm add @kishannareshpal/expo-pdf # bun add @kishannareshpal/expo-pdf # pnpm add @kishannareshpal/expo-pdf # yarn add @kishannareshpal/expo-pdf
-
Install CocoaPods dependencies
npx pod-install
import { PdfView } from '@kishannareshpal/expo-pdf';
export const App = () => {
const [assets] = useAssets([require('./assets/sample.pdf')]);
const [uri, setUri] = (useState < string) | (null > null);
useEffect(() => {
if (!assets?.length) {
return;
}
assets[0]
.downloadAsync()
.then((asset) => setUri(asset.localUri))
.catch(console.error);
}, [assets]);
if (!uri) {
return null;
}
return <PdfView style={{ flex: 1 }} uri={uri} />;
};import { File } from 'expo-file-system';
import * as DocumentPicker from 'expo-document-picker';
import { PdfView } from '@kishannareshpal/expo-pdf'
export const App = () => {
const [uri, setUri] = useState<string | null>(null)
const pickFile = () => {
try {
const result = await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: true });
const file = new File(result.assets[0]);
setUri(file.localUri)
} catch (error) {
console.error(error);
}
}
if (!uri) {
return null;
}
return (
<View>
<Button onPress={pickFile}>Pick a file</Button>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{uri ? (
<PdfView
style={{ flex: 1 }}
uri={uri}
/>
) : (
<Text>Please pick a file<Text>
)}
</View>
</View>
)
}import { File } from 'expo-file-system';
import * as DocumentPicker from 'expo-document-picker';
import { PdfView } from '@kishannareshpal/expo-pdf'
export const App = () => {
const [uri, setUri] = useState<string | null>(null)
const loadFromUrl = (url: string) => {
const destination = new Directory(Paths.cache, 'pdfs');
try {
destination.create();
const output = await File.downloadFileAsync(url, destination);
setUri(output.uri)
} catch (error) {
console.error(error);
}
}
if (!uri) {
return null;
}
return (
<View>
<Button onPress={() => loadFromUrl("https://pdfobject.com/pdf/sample.pdf")}>
Download and load from URL
</Button>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{uri ? (
<PdfView
style={{ flex: 1 }}
uri={uri}
/>
) : (
<Text>Please download the file to preview<Text>
)}
</View>
</View>
)
}| Props | Required | Type | Description | Default |
|---|---|---|---|---|
uri |
Required | string |
PDF document local file URI. | - |
password |
No | string |
Password to apply for password-protected PDFs by on load. | undefined |
pagingEnabled |
No | boolean |
Enables page-by-page snapping instead of free scrolling in vertical direction by default, unless the horizontal prop is provided to page horizontally instead. |
false |
doubleTapToZoom |
No | boolean |
Allows the user to zoom in/out by double-tapping the document. | true |
horizontal |
No | boolean |
Renders and scrolls pages horizontally instead of vertically. If paging is enabled, then it changes horizontally. | false |
pageGap |
No | number |
Space between adjacent pages. | 0 |
contentPadding |
No | ContentPadding |
Padding applied around the document inside the viewer container. | { top: 0, left: 0, right: 0, bottom: 0 } |
fitMode |
No | FitMode |
How the document is scaled to fit within the viewer. | "width" |
pageColorInverted |
No | boolean |
Whether the rendered page should have its color inverted (useful to simulate dark mode) | false |
autoScale |
No | boolean |
Automatically rescales the document when its or its parent’s layout changes after initial render. | true |
onLoadComplete |
No | (OnLoadCompleteEventPayload) => void |
Triggered once the document and its metadata have finished loading. | - |
onPageChanged |
No | (OnPageChangedEventPayload) => void |
Triggered when the currently visible page index changes. | - |
onError |
No | (OnErrorEventPayload) => void |
Triggered when the PDF fails to load, decrypt, or render. | - |
{ top?: number, left?: number, right?: number, bottom?: number }'width' | 'height' | 'both';{
pageCount: number;
}{ pageIndex: number, pageCount: number }{
code: 'invalid_url' | 'invalid_document' | 'password_required' | 'password_incorrect',
message: string
}To use uniwind class names, you must wrap the component with the withUniwind Higher-Order Component (HOC).
This allows the component to process the className prop and convert it into native styles.
// src/lib/styled.tsx
import { PdfView as PdfViewPrimitive } from '@kishannareshpal/expo-pdf';
import { ComponentProps } from 'react';
import { withUniwind } from 'uniwind';
export const StyledPdfViewPrimitive = withUniwind(PdfViewPrimitive);
export type StyledPdfViewPrimitiveProps = ComponentProps<
typeof StyledPdfViewPrimitive
>;// src/app.tsx
import { StyledPdfView } from '@/lib/styled';
const App = () => {
return (
<StyledPdfView
className="flex-1 rounded-4xl bg-red-500"
uri="..."
...
/>
)
}This project is inspired by react-native-pdf and maintained to the more modern React Native and Expo ecosystem.
Contributions are welcome!
Please read CONTRIBUTING.md
NOTE
This package follows semantic versioning with the format:
major.minor.patch(-beta.n|next).
- Major version: Increment when making incompatible API changes.
- Minor version: Increment when adding new functionality in a backward-compatible way.
- Patch version: Increment when fixing bugs in a backward-compatible manner.
Only approved repository members with access to our NPM account can publish new versions.
- Ensure all changes ready for release are merged into
main. - Go to the
✳️ Release Packageworkflow - Click the
Run workflowdropdown button and configure the inputs:Dry run: Uncheck this to perform the actual release (Default istrueto test the build/versioning without publishing - only useful when updating therelease.ymlworkflow during maintenance)NPM tag: Leave aslatestfor standard releases, or usebeta/nextfor pre-releases.Version override: Leave asautoto let it automatically increment the patch version (or beta version ifbetawas specified above). if you to bump the minor or major version, you must specify that here (e.g.1.2.0or2.0.0)
- Click the
Run workflowbutton to publish 🚀- Monitor the status at kishannareshpal/expo-pdf/actions
MIT


