How-to guides

Add Printing and PDF Export to an Expo App

Use Expo Print to open the system print sheet or turn an HTML document into a PDF. Keep printing, PDF creation and permanent storage as separate actions so the interface reports what actually happened.

Updated 28 October 2026

What you will build

A receipt screen with Print and Export PDF buttons. Print opens the platform’s printer flow. Export PDF creates a file and opens the share sheet when sharing is available. A print dialog opening is not confirmation that paper came out of a printer.

  1. Step 1Build the document
  2. Step 2Print or make a PDF
  3. Step 3Share or save it

The example targets native iOS and Android using the Expo Print and Sharing APIs documented for SDK 58. Web printing follows browser behavior and does not provide the same PDF-file workflow.

Build it in Newly

Open your project in Newly and describe the behavior you want. Use this suggested prompt as a starting point, then review the generated changes and follow the checks below.

Suggested prompt
Add Print receipt and Export PDF actions to the order-details screen using expo-print and expo-sharing. Build a complete HTML receipt from the selected order, escape customer-supplied text, and include item quantities, prices and the total. Show a busy state and handle cancellation or errors. Do not mark an order as printed merely because the print sheet opened. Offer sharing for the generated PDF and keep temporary files separate from documents the customer expects to retain.

  1. 1. Make a complete receipt document

    Start with a small synthetic order and a full HTML document. Keep the receipt’s content separate from the screen layout so navigation controls do not appear on paper. When you substitute real customer text, escape it before inserting it into HTML. Check long product names, page breaks and totals.

  2. 2. Wire Print and Export PDF separately

    Call Print.printAsync for the printer flow. Call Print.printToFileAsync for a PDF and pass its URI to your sharing flow. Check sharing availability before presenting an Export PDF action that promises a share sheet. Avoid treating either action as a payment or fulfillment confirmation.

  3. 3. Choose how long the document must last

    The generated PDF is a temporary cache file. If the customer needs an invoice history, save the document through your app’s durable storage or account backend and provide a way to reopen it. Test that history after restarting the app instead of relying on the last generated URI.

Expo implementation example

Install the dependencies in your Expo project. Expo resolves native package versions for your installed SDK.

Install command
npx expo install expo-print expo-sharing

Code example
import { useRef, useState } from "react";
import { Button, Text, View } from "react-native";
import * as Print from "expo-print";
import * as Sharing from "expo-sharing";

const html = `<!DOCTYPE html>
<html><head><meta charset="utf-8" />
<title>Example receipt</title>
<style>body { font: 16px sans-serif; padding: 24px; }</style>
</head><body><h1>Example receipt</h1>
<p>Order DEMO-001</p><p>Notebook × 2: $12.00</p>
<p><strong>Total: $12.00</strong></p></body></html>`;

export default function ReceiptActions() {
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState("");
  const running = useRef(false);

  async function act(exportPdf: boolean) {
    if (running.current) return;
    running.current = true;
    setBusy(true);
    setStatus("");
    try {
      if (exportPdf) {
        if (!(await Sharing.isAvailableAsync())) {
          setStatus("Sharing is unavailable on this device.");
          return;
        }
        const file = await Print.printToFileAsync({ html });
        await Sharing.shareAsync(file.uri, { mimeType: "application/pdf" });
        setStatus("PDF created. The sharing sheet has closed.");
      } else {
        await Print.printAsync({ html });
        setStatus("Check the system print flow for the job status.");
      }
    } catch {
      setStatus("The action was cancelled or could not finish. Try again.");
    } finally {
      running.current = false;
      setBusy(false);
    }
  }

  return (
    <View style={{ gap: 16, padding: 24 }}>
      <Button title="Print receipt" disabled={busy} onPress={() => act(false)} />
      <Button title="Export PDF" disabled={busy} onPress={() => act(true)} />
      <Text accessibilityLiveRegion="polite">{status}</Text>
    </View>
  );
}

The receipt is synthetic and contains no customer data. Escape dynamic values before inserting them into HTML. The PDF is created in cache; sharing does not guarantee that the recipient saved it or that the app retains a permanent copy.

Test before publishing

  • Print the synthetic receipt to a real printer and check the paper layout and totals.
  • Cancel the print sheet; the app stays usable and does not mark the order as fulfilled or printed.
  • Export a PDF and open it from the destination app rather than only checking for a returned URI.
  • Try a multi-page receipt and a long customer or item name.
  • If you add a logo, confirm it appears in the PDF on both platforms; iOS HTML printing has restrictions on local image URLs.

Run these checks in your own project on iOS and Android. A browser preview does not establish native permission, hardware or background behavior.

Common problems

The print promise resolves before anything prints

Expo documents different promise behavior on Android and iOS. Android resolves after displaying the print window, so do not use resolution as proof of a completed physical job.

A logo is missing from an iPhone PDF

Expo notes that iOS HTML printing does not support local asset URLs. Use a supported image source, such as an inlined base64 image, and inspect the generated document on a device.

Questions about print support

Does printing need a custom printer picker?

Usually no. Print.printAsync opens the system printing interface. Expo also exposes selectPrinterAsync on iOS when the product needs to choose and remember a printer separately.

Is generating a PDF the same as saving it permanently?

No. Print.printToFileAsync creates a cache file on native platforms. Use durable app storage, an account backend or an explicit user save flow when the document must remain available.

Can a print job start without a printer?

You can create a PDF without a physical printer. Physical printing depends on an available printer and the platform’s print services. Test the printer model used by your customers.

Will this behave the same in a web preview?

No. Expo documents browser printing behavior on web, including opening a print dialog for printToFileAsync. Validate the native PDF and sharing flow on iOS and Android.

Sources and API references

Keep building

Build this feature in Newly