Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
For an Expo app, use expo-print: build a complete HTML document, call Print.printToFileAsync({ html }), then move the returned cache file to durable app storage if you need it to persist. You can share that file with expo-sharing. For a bare React Native app, a native module such as react-native-html-to-pdf is an alternative, but its compatibility and setup depend on the version you use.
Choose the PDF approach for your React Native app
| Approach | Best fit | Important constraints |
|---|---|---|
expo-print |
Expo projects that need to turn HTML into a PDF on-device. | iOS HTML printing does not support local asset URLs; Android rendering and margins depend in part on the WebView engine. See Expo Print documentation. |
| Native WebView printing | Custom native implementations needing direct control over Android or iOS printing. | On Android, wait for onPageFinished() before creating the print job. Android’s HTML-printing options do not support headers or footers, page ranges, JavaScript-triggered printing, or CSS print attributes such as landscape. See Android’s HTML printing guide. |
react-native-html-to-pdf |
Bare React Native or custom-native projects where a native module fits the app’s setup. | Pin and verify the package version and native configuration. Its README says iOS accepts only Documents as a custom directory. See the package README. |
This guide focuses on Expo because expo-print provides the most direct route from an HTML string to a PDF file in an Expo app. The code uses Expo’s current File/Paths file API; filesystem APIs differ across SDK generations, so use the API supported by your installed Expo SDK.
Generate, save, and share a PDF with Expo
Install the packages
Install expo-print, expo-file-system, and expo-sharing using the package-install workflow for your Expo project. Check the documentation matching your Expo SDK before copying filesystem calls: newer SDKs expose File and Paths, while older projects may use the legacy moveAsync API.
Use a complete HTML document
Pass printToFileAsync a full HTML document rather than a fragment. A doctype, viewport, and explicit print margins make the rendered output more predictable. The following TypeScript helper moves the generated PDF out of the cache directory and shares it when sharing is available:
#1 Best Overall
import * as Print from 'expo-print';
import { File, Paths } from 'expo-file-system';
import * as Sharing from 'expo-sharing';
export async function htmlToPdf(name: string, body: string) {
const html = `<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<style>
@page { margin: 20px; }
body { font-family: sans-serif; }
</style>
</head>
<body>${body}</body>
</html>`;
const { uri } = await Print.printToFileAsync({ html });
const destination = new File(Paths.document, name);
const source = new File(uri);
await source.move(destination);
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(destination.uri, { mimeType: 'application/pdf' });
}
return destination.uri;
}
The returned uri from printToFileAsync points to a PDF in the app’s cache directory. Moving it to the document directory is useful when the file needs to remain available beyond cache cleanup. Sharing.isAvailableAsync() lets the app avoid trying to open a share action where sharing is unavailable. Expo documents the PDF generation, file handling, and share behavior in Print, FileSystem, and Sharing.
Escape dynamic content before adding it to HTML
The helper accepts body as HTML. Do not interpolate untrusted text directly into markup: escape characters such as &, <, >, ", and ' for their HTML contexts. If the content is already generated from trusted templates, still make sure user-provided fields are escaped before insertion. HTML-to-PDF conversion renders markup; it is not a sanitizer.
Handle images, fonts, layout, and page breaks
Local images on iOS
Expo explicitly warns that printing from HTML source on iOS does not support local asset URLs because of WKWebView limitations. A path such as file://... that works elsewhere may therefore be missing in the PDF. Convert local images to base64 and reference them with a data URL, for example data:image/png;base64,..., in the HTML.
Rank #2
Remote HTTPS images may work, but the app needs network access and the image must finish loading before the document is printed. If a document must be reproducible offline, embed its images rather than relying on remote URLs. Test the actual image formats and document size used by your app; no universal size or performance threshold is established by the platform documentation.
Margins, page size, and page breaks
Use CSS @page margins to set a baseline for print layout. Expo notes that Android margins can depend on the WebView engine; iOS exposes a margins option. Android’s native HTML-printing recipe does not support CSS print attributes such as landscape, so don’t assume CSS alone will provide identical layout control on both platforms.
For multipage content, style page breaks deliberately and verify the result on physical page boundaries. Check long tables, headings near the bottom of a page, oversized images, and content that can grow unexpectedly. A well-formed document beginning with <!DOCTYPE html> also helps avoid a blank trailing page when using iOS markup formatting.
Rank #3
Fonts and viewport
Set a viewport and explicit fonts, widths, and spacing in the HTML and CSS. Rendering occurs through platform web-view/print behavior, so verify line wrapping, font availability, and page count on both iOS and Android rather than assuming a browser preview will match the generated PDF.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Use a native WebView or a bare React Native module
Android: wait for the page to finish loading
For custom Android printing, load the HTML in a WebView and create the print job only after its onPageFinished() callback. Android Developers warns that skipping this wait can result in incomplete or blank output, or failure. This matters especially if the page includes images or other resources that have to load before printing.
Android’s documented HTML-printing path does not provide headers or footers, page ranges, JavaScript-triggered printing, or CSS print attributes such as landscape. If your design depends on these controls, check whether the chosen platform API or a different document-generation approach meets that requirement before investing in a custom bridge.
Rank #4
iOS: use WebKit PDF generation when building a native bridge
Apple’s WKWebView.pdf(configuration:) API asynchronously generates PDF data from the web view’s contents. The completion-handler form is createPDF(configuration:completionHandler:). These APIs are options when implementing a native iOS module or bridge; they are not a drop-in replacement for Expo’s JavaScript API. See Apple’s WKWebView PDF documentation.
Bare React Native: check native-module compatibility
The react-native-html-to-pdf README demonstrates generating a PDF from an HTML string with generatePDF. Its native dependencies and configuration are version-sensitive; pin the version your app uses and verify the installation against the current project setup. The README notes that on iOS, Documents is the only accepted custom directory value.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Test the PDF workflow on both platforms
- Build a complete HTML document with a doctype, viewport, explicit fonts and widths, and an
@pagemargin rule. - For local iOS images, convert assets to base64 data URLs instead of relying on local asset paths.
- Generate the PDF with
Print.printToFileAsyncin Expo, or with the native module or bridge selected for a bare project. - In custom Android WebView code, wait for
onPageFinished()before starting the print job. - Move a generated cache file to durable app storage if it must survive cache cleanup.
- Check
Sharing.isAvailableAsync()before offering the Expo share action. - Test long documents, page breaks, fonts, images, landscape needs, and offline behavior on iOS and Android.
Troubleshoot common output problems
| Symptom | Likely cause | What to check |
|---|---|---|
| PDF is blank or incomplete on Android. | The print job started before the WebView finished loading. | In a custom implementation, start printing from onPageFinished(), as Android’s HTML-printing guide directs. |
| Local image is absent on iOS. | HTML printing through Expo does not support local asset URLs on iOS. | Embed the image as a base64 data URL; test remote images separately with network available and enough load time. |
| Margins differ between platforms. | Android WebView engine can affect page margins. | Set an @page margin and verify on target devices; Expo exposes a margins option on iOS. |
| A blank page appears at the end on iOS. | Markup formatting or document structure may be contributing. | Start with a well-formed document and <!DOCTYPE html>; inspect content and page-break rules. |
| The PDF disappears later. | The generated file was left in the cache directory. | Move it to durable app storage if it needs to outlive cache cleanup. |
| Sharing fails or is unavailable. | The platform does not currently offer a share action. | Check Sharing.isAvailableAsync() and only call shareAsync when it returns true. |
| Landscape, page ranges, or headers/footers are missing in Android output. | Those controls are not supported by Android’s documented HTML-printing options. | Confirm the selected native path satisfies the document requirements before implementation; do not rely on unsupported CSS print attributes. |
Or skip the browser setup
If the PDF you need is a screenshot of a public web page rather than a document rendered inside your app, ScreenshotNeo offers a website screenshot API and MCP server. One GET request can return an image or PDF. For example, request a PDF using its documented output option and save the response:
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://stripe.com
-d format=pdf
-o page.pdf
See the ScreenshotNeo API documentation for supported parameters. It accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to try 1,000 screenshots a month with no card.
Frequently Asked Questions
Does Expo generate the PDF in the app or on a server?
The documented Print.printToFileAsync flow prints the supplied HTML and saves the PDF in the app’s cache directory.
Can I use this Expo approach in a bare React Native app?
The Expo API is intended for Expo projects. Bare projects can use a native-module option such as react-native-html-to-pdf or implement platform printing, subject to the chosen version and native configuration.
Can I print a local image on iOS with expo-print?
Not through a local asset URL in HTML source. Embed the image as a base64 data URL instead.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

