React Native HTML to PDF: expo-print, native modules, and server rendering
In React Native, convert HTML to PDF with expo-print's printToFileAsync (the Expo module, 423,000 weekly npm downloads) or react-native-html-to-pdf for bare React Native projects. Both render with the operating system's webview, so complex layouts can differ between iOS and Android; when the document must look identical everywhere, render it server-side and download the file instead.
That last clause is the part every tutorial skips. On-device PDF generation is genuinely great (offline, private, fast for receipts and tickets), but it delegates rendering to whatever web engine the OS provides, and iOS and Android do not agree on the details. If you've ever shipped an invoice that looked right on your iPhone and wrong on a customer's Pixel, you've met the problem this guide is structured around. (Rendering PDFs in a React web app instead? That's our React HTML-to-PDF guide.)
Three routes, working code for each, and an honest rule for choosing.
Key Takeawaysexpo-print is the mainstream answer: 423k weekly downloads, actively maintained on the Expo SDK release train, and it works in managed Expo apps with no native configuration.react-native-html-to-pdf covers bare RN (39.5k weekly downloads, last published September 2025); avoid its stale forks, including the "lite" variant frozen since 2021.On-device rendering differs between iOS and Android: WKWebView and Android's print stack disagree on fonts, page breaks, and CSS details, so pixel-perfect cross-platform output isn't achievable locally.Server-side rendering fixes consistency: one API call renders on headless Chrome, identically for every user, and reuses the same template your backend already has.Rule of thumb: offline and disposable → on-device; branded, archived, or shared documents → server-side.
How do I convert HTML to PDF in React Native?
Use expo-print in Expo apps: Print.printToFileAsync({ html }) returns a file URI you can share or upload. In bare React Native, react-native-html-to-pdf does the same via RNHTMLtoPDF.convert(). Both convert an HTML string on the device using the OS webview; for server-consistent output, POST the HTML to a rendering API and download the PDF instead.
Option 1: expo-print (most apps)
expo-print ships with the Expo SDK, needs no config plugin, and pairs naturally with expo-sharing:
import * as Print from "expo-print";
import * as Sharing from "expo-sharing";
const html = `
<html>
<body style="font-family: -apple-system, Roboto, sans-serif">
<h1>Receipt #1042</h1>
<p>Total: $1,280.00</p>
</body>
</html>`;
export async function exportReceipt() {
const { uri } = await Print.printToFileAsync({ html });
await Sharing.shareAsync(uri, { mimeType: "application/pdf" });
}
That's a complete, offline-capable PDF export in a dozen lines. printToFileAsync also accepts width, height, and margin options for page geometry, and on iOS you can generate from a URL as well as a string.
At 423,000 weekly downloads and versioned with each Expo SDK (v57 as of July 2026), it's the most-used and best-maintained option in the ecosystem, which makes its near-absence from search results genuinely strange.
Option 2: react-native-html-to-pdf (bare React Native)
For apps outside Expo, react-native-html-to-pdf is the established native module:
import RNHTMLtoPDF from "react-native-html-to-pdf";
const { filePath } = await RNHTMLtoPDF.convert({
html: "<h1>Receipt #1042</h1><p>Total: $1,280.00</p>",
fileName: "receipt-1042",
base64: false,
});
// filePath → hand to react-native-share, upload, or open
It does the same job through the same OS engines. Two ecosystem notes: it moves slower than expo-print (v1.3.0, last published September 2025, with autolinking support for current RN versions), and its search results are littered with forks: "-lite" (frozen since 2021), "-custom", and several personal copies. Fork sprawl is what unmaintained-adjacent upstreams produce; stick to the canonical package and check its issue tracker against your RN version before committing.
The catch: iOS and Android don't render identically
Both options above are wrappers around "ask the OS to print HTML." On iOS that's WKWebView feeding the UIKit print pipeline; on Android it's the system WebView and print framework. They're different engines with different versions across devices, and it shows:
- Fonts: system font stacks differ (
-apple-systemvs Roboto), and custom-font loading inside the print webview is inconsistent per platform. - Page breaks:
break-inside: avoidand friends are honored differently; long tables are the classic casualty. - CSS details: shadows, gradients, and flex quirks vary with the device's WebView version, which on Android is updated independently of your app.
For a receipt someone glances at, none of this matters. For a branded invoice that finance archives, a report your customer forwards, or anything a designer signed off on, "roughly the same on both platforms" is a bug you'll rediscover with every OS update.
Option 3: render server-side for identical output
The consistency fix is to take rendering off the device entirely: POST the HTML to an API that renders on headless Chrome, and every user gets the same bytes.
// SDK 54+ moved the classic API; the legacy import keeps it available
import * as FileSystem from "expo-file-system/legacy";
import * as Sharing from "expo-sharing";
export async function exportInvoice(invoiceHtml) {
const res = await fetch("https://api.transformy.io/v1/pdf/chrome", {
method: "POST",
headers: {
// Call via your backend in production; don't ship API keys in the app bundle
Authorization: `Bearer ${process.env.EXPO_PUBLIC_DEMO_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ html: invoiceHtml, page_size: "A4" }),
});
const pdf = await res.blob();
const uri = FileSystem.cacheDirectory + "invoice.pdf";
await FileSystem.writeAsStringAsync(
uri,
await blobToBase64(pdf), // small helper: FileReader → base64 string
{ encoding: FileSystem.EncodingType.Base64 }
);
await Sharing.shareAsync(uri, { mimeType: "application/pdf" });
}
One security note baked into the comment above: mobile bundles are extractable, so production apps should route the call through their own backend (which holds the key) rather than embedding it. That backend hop has a bonus: if your server already renders the same invoice template for the web app, mobile now reuses it byte-for-byte; the Node.js HTML-to-PDF guide shows that server side, and the HTML to PDF API docs cover parameters like footers with page numbers, which no on-device route offers.
When this route wins: branded or regulated documents, anything archived or emailed onward, reports too heavy for a phone webview, and templates shared with your web product. When it doesn't: offline-first flows; on-device is the only option on a plane.
Saving and sharing the PDF
Whichever route produced the file:
- Share sheet:
expo-sharing'sshareAsync(uri)orreact-native-sharefor bare RN. - Email attachment: share-sheet providers handle it, or upload server-side and send from your backend.
- Persistent storage: move it out of the cache directory (
expo-file-system) or use Android's scoped-storage APIs via a library; PDFs in cache directories get evicted. - Upload:
uploadAsync(from the legacy expo-file-system API) or a multipart fetch; with a server-side render you can skip the round trip entirely and have the API deliver straight to your own S3/GCS bucket.
FAQ
How do I generate a PDF in Expo without ejecting?
Use expo-print: printToFileAsync({ html }) works in managed Expo apps with no native code or config plugins, and pairs with expo-sharing for the share sheet. It's part of the Expo SDK, so it stays compatible with each SDK upgrade.
Is react-native-html-to-pdf still maintained?
Slowly. The canonical package last published v1.3.0 in September 2025 and still works with autolinking on current React Native, but the ecosystem around it is full of frozen forks; avoid the "-lite" variant (2021) and check open issues against your RN version.
Can I get identical PDF output on iOS and Android?
Not from on-device rendering: iOS and Android use different web engines with device-dependent versions. If identical output matters, render server-side on a fixed engine (headless Chrome via an API) and download the result; our best HTML to PDF APIs comparison covers the options.
Does HTML to PDF work offline in React Native?
Yes, that's on-device generation's superpower: expo-print and react-native-html-to-pdf both convert with no network at all. The tradeoff is platform-dependent rendering; pick per document type, and nothing stops you using both routes in one app.
Conclusion
The decision comes down to what the document is for:
- Expo app, quick receipts/tickets, offline needed → expo-print.
- Bare React Native, same use cases → react-native-html-to-pdf (the canonical package, not a fork).
- Branded, archived, or cross-platform-critical documents → server-side rendering via your backend and an API, reusing your web templates.
- Real apps often ship both: local for the disposable, server for the official.
If the server route is on your map, Transformy's HTML to PDF API renders on headless Chrome with a free tier of 100 documents a month: enough to wire the backend endpoint and A/B your invoice against both phones today. Get a free key and send the template your designer actually made, not the demo HTML.