react-native-pdfrender
v1.0.3
Published
React Native PDF rendering library with TurboModules and Fabric components
Maintainers
Readme
react-native-pdfrender
A production-ready React Native PDF rendering library built on the New Architecture (TurboModules + Fabric components).
- Embedded viewer — drop
<PdfViewerView />anywhere in your render tree - Imperative viewer — use the
usePdfViewerhook to open a modal-style PDF viewer - Native rendering — CGPDFDocument / Core Graphics on iOS, Android PdfRenderer on Android
- Jetpack Compose — modern Android UI, no XML layouts
- Codegen-driven — TypeScript specs generate type-safe native bindings at build time
- Zero JS dependencies — no third-party npm packages required at runtime
Requirements
| Platform | Minimum version | |----------|----------------| | React Native | 0.70+ | | iOS | 13.0+ | | Android | SDK 24+ (Android 7.0) | | Node | 18+ |
Installation
npm install react-native-pdfrender
# or
yarn add react-native-pdfrenderiOS
cd ios && pod installAndroid
No extra steps — auto-linking handles it.
Usage
1. Declarative (embedded viewer)
import { PdfViewerView } from 'react-native-pdfrender';
function MyScreen() {
return (
<PdfViewerView
uri="file:///path/to/document.pdf"
initialPage={0}
defaultZoom={1.5}
isFullScreen={false}
style={{ flex: 1 }}
onZoomChange={(pct) => console.log('zoom:', pct)}
onFullScreenChange={(fs) => setFullScreen(fs)}
/>
);
}2. Imperative (hook — modal viewer)
import { usePdfViewer } from 'react-native-pdfrender';
function MyScreen() {
const { open, dismiss, getPageCount, createPdf } = usePdfViewer({
onFullScreenChanged: (isFS) => console.log('fullscreen:', isFS),
onZoomChanged: (zoom) => console.log('zoom:', zoom),
onDismiss: () => console.log('viewer closed'),
});
return (
<>
<Button title="Open PDF" onPress={() => open('file:///path/to/doc.pdf')} />
<Button title="Dismiss" onPress={dismiss} />
<Button title="Page count" onPress={async () => {
const n = await getPageCount('file:///path/to/doc.pdf');
console.log('pages:', n);
}} />
</>
);
}API
<PdfViewerView />
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| uri | string | required | file:// or content:// path to the PDF |
| initialPage | number | -1 (first) | 0-based page index to open on |
| defaultZoom | number | 1.5 | Initial zoom level (1.0 = 100%) |
| isFullScreen | boolean | false | Fills the parent container when true |
| rightLayout | boolean | false | Positions viewer on right side (split-view) |
| maxWidth | number | — | Max pixel width (split-view) |
| screenWidthPercentage | number | 100 | % of screen width |
| icons | IconSet | — | Custom toolbar icon assets |
| iconSize | number | 40 | Icon size in logical pixels |
| backButtonText | string | — | Toolbar back button label |
| headerText | string | — | Toolbar header label |
| style | ViewStyle | — | Container style |
| onFullScreenChange | (isFullScreen: boolean) => void | — | Fires when fullscreen state changes |
| onZoomChange | (zoomPercentage: number) => void | — | Fires when zoom changes |
| onLeftScreenChange | (isLeftScreen: boolean) => void | — | Fires in split-view |
| onRightScreenChange | (isRightScreen: boolean) => void | — | Fires in split-view |
IconSet
interface IconSet {
zoomIn?: number | string; // require('./zoom_in.png') or URI
zoomOut?: number | string;
fullScreen?: number | string;
minimizeScreen?: number | string;
leftLayout?: number | string;
rightLayout?: number | string;
}usePdfViewer(options?)
interface UsePdfViewerOptions {
onFullScreenChanged?: (isFullScreen: boolean) => void;
onZoomChanged?: (zoomPercentage: number) => void;
onDismiss?: () => void;
onError?: (error: string) => void;
}
interface UsePdfViewerReturn {
open: (uri: string, pageIndex?: number, defaultZoom?: number) => void;
dismiss: () => void;
getPageCount:(uri: string) => Promise<number>;
createPdf: (pages: PdfPage[], widthPx: number, heightPx: number) => Promise<PdfCreationResult>;
}NativePdfViewerModule (low-level TurboModule)
Exposed for advanced use cases. Prefer usePdfViewer for most scenarios.
import { NativePdfViewerModule } from 'react-native-pdfrender';
NativePdfViewerModule.openPdfViewer(uri, pageIndex, defaultZoom);
NativePdfViewerModule.dismissPdfViewer();
const count = await NativePdfViewerModule.getPdfPageCount(uri);
const result = await NativePdfViewerModule.createMultiPagePdfBase64(pages, width, height);Working with File URIs
The uri prop accepts a local file path only. The library reads the file directly from disk using native APIs (CGPDFDocument on iOS, Android PdfRenderer on Android). Remote URLs are not loaded automatically — you must download the file first and pass the local path.
Local files — Android
Android document pickers (e.g. react-native-document-picker) return a content:// URI. Pass it directly:
import DocumentPicker, { types } from 'react-native-document-picker';
const result = await DocumentPicker.pickSingle({ type: types.pdf });
// result.uri → "content://com.android.providers.media.documents/document/..."
<PdfViewerView uri={result.uri} />For files already on disk, use a file:// URI:
// File in the app's documents directory
const path = `file:///data/user/0/${bundleId}/files/document.pdf`;
<PdfViewerView uri={path} />Local files — iOS
iOS document pickers return a file:// URI. Pass it directly:
import DocumentPicker, { types } from 'react-native-document-picker';
const result = await DocumentPicker.pickSingle({ type: types.pdf });
// result.uri → "file:///var/mobile/Containers/Data/Application/.../document.pdf"
<PdfViewerView uri={result.uri} />For files in the app bundle or documents directory:
import { Platform } from 'react-native';
import RNFS from 'react-native-fs';
const path = `file://${RNFS.DocumentDirectoryPath}/document.pdf`;
<PdfViewerView uri={path} />iOS note: Only
file://URIs are supported on iOS. There is nocontent://scheme on iOS.
Remote URLs — download first, then render
The library does not fetch URLs. Download the file to local storage first, then pass the local path.
Option A — using react-native-blob-util
npm install react-native-blob-util
cd ios && pod installimport RNBlobUtil from 'react-native-blob-util';
import { useState } from 'react';
import { PdfViewerView } from 'react-native-pdfrender';
export default function PdfFromUrl() {
const [localUri, setLocalUri] = useState<string | null>(null);
const [downloading, setDownloading] = useState(false);
async function downloadAndOpen(url: string) {
setDownloading(true);
try {
const dir = RNBlobUtil.fs.dirs.DocumentDir;
const dest = `${dir}/downloaded.pdf`;
// Download the file
const res = await RNBlobUtil.config({ path: dest }).fetch('GET', url);
// Pass the local file:// path to the viewer
setLocalUri(`file://${res.path()}`);
} catch (e) {
console.error('Download failed:', e);
} finally {
setDownloading(false);
}
}
return (
<>
{downloading && <Text>Downloading…</Text>}
{localUri && <PdfViewerView uri={localUri} style={{ flex: 1 }} />}
</>
);
}Option B — using react-native-fs
npm install react-native-fs
cd ios && pod installimport RNFS from 'react-native-fs';
import { useState } from 'react';
import { PdfViewerView } from 'react-native-pdfrender';
export default function PdfFromUrl() {
const [localUri, setLocalUri] = useState<string | null>(null);
async function downloadAndOpen(url: string) {
const dest = `${RNFS.DocumentDirectoryPath}/downloaded.pdf`;
await RNFS.downloadFile({
fromUrl: url,
toFile: dest,
}).promise;
setLocalUri(`file://${dest}`);
}
return localUri ? (
<PdfViewerView uri={localUri} style={{ flex: 1 }} />
) : null;
}URI quick reference
| Source | Android URI | iOS URI |
|--------|-------------|---------|
| Document picker | content://... ✅ | file://... ✅ |
| App documents dir | file:///data/user/0/<pkg>/files/file.pdf | file://<RNFS.DocumentDirectoryPath>/file.pdf |
| Remote URL | Download first → use local path | Download first → use local path |
| http:// / https:// | ❌ Not supported | ❌ Not supported |
Architecture
react-native-pdfrender/
├── android/
│ ├── build.gradle ← library module (com.android.library)
│ └── src/main/java/com/pdfrender/
│ ├── PdfViewerTurboModule.kt ← TurboModule (imperative API)
│ ├── PdfViewerFabricManager.kt ← Fabric ViewManager (embedded viewer)
│ ├── PdfViewerFragment.kt ← modal viewer Fragment
│ ├── PdfRenderingLogic.kt ← Android PdfRenderer wrapper
│ ├── ComposeRenderer.kt ← Jetpack Compose UI
│ └── events/ ← Fabric event classes
├── ios/
│ ├── PdfViewerTurboModule.mm ← ObjC++ TurboModule bridge
│ ├── PdfViewerTurboModuleImpl.swift ← Swift business logic
│ ├── PdfViewerComponentView.mm ← ObjC++ Fabric ViewManager
│ ├── PdfViewerView.swift ← main PDF rendering view
│ ├── PdfRenderingLogic.swift ← CGPDFDocument wrapper
│ └── ...
├── src/
│ ├── index.tsx ← public API exports
│ ├── PdfViewerView.tsx ← React component
│ ├── usePdfViewer.ts ← hook
│ └── specs/
│ ├── NativePdfViewerModule.ts ← TurboModule codegen spec
│ └── NativePdfViewerComponent.ts ← Fabric codegen spec
├── lib/ ← compiled output (generated by bob)
├── example/ ← example React Native app
└── react-native-pdfrender.podspecNew Architecture (codegen)
The codegenConfig in package.json points to src/specs/. At build time:
- iOS — CocoaPods generates
PdfViewerSpecs.hand the Fabric C++ headers - Android — Gradle generates the abstract
NativePdfViewerModuleSpecKotlin class
Building the library
# Install deps
yarn install
# Compile TypeScript → lib/
yarn build
# runs automatically as `prepare` before npm publish
# Type-check only
yarn typecheckRunning the example app
Install dependencies
# Library deps
yarn install
# Example deps
cd example && yarn installiOS (one-time Xcode project cleanup required)
The example Xcode project was migrated from the original monolithic app and still includes the library Swift files in the app target. Because CocoaPods now compiles those same files as part of the library pod, you must remove them from the app target first to avoid duplicate-symbol errors:
- Open
example/ios/pdfRender.xcodeprojin Xcode - Select the pdfRender app target → Build Phases → Compile Sources
- Remove these files (click each, press
–):PdfViewerView.swift,PdfViewerViewController.swiftPdfViewerTurboModuleImpl.swift,PdfRenderingLogic.swiftPdfPageView.swift,PdfToolbarView.swiftPdfCacheManager.swift,PdfConstants.swiftPdfViewerTurboModule.mm,PdfViewerComponentView.mm
- Save the Xcode project, then:
cd example/ios && pod install && cd ../..
yarn example:iosAndroid
yarn example:androidPublishing to npm
# Build the library
yarn prepare
# Verify what will be published
npm pack --dry-run
# Publish publicly
npm publish --access publicCommon Issues
PdfViewerSpecs/PdfViewerSpecs.h not found (iOS build)
Run pod install inside example/ios/ (or the consuming app's ios/). This triggers codegen which produces the PdfViewerSpecs headers.
Duplicate symbol errors on iOS
Follow the Xcode project cleanup steps above.
NativePdfViewerModuleSpec not found (Android)
Run the app once — Gradle auto-generates the spec. Or run manually:
cd example/android && ./gradlew generateCodegenArtifactsFromSchemaMetro can't resolve react-native-pdfrender
Run yarn install inside example/ (not just the root). The file:../ entry creates a symlink in example/node_modules/react-native-pdfrender that Metro follows.
License
MIT © Shubham Keshari
