---
title: How to Scan a Document to PDF in a Capacitor App
description: Learn how to scan a document to PDF in a Capacitor app with one call, then open, print, share, and keep the PDF on Android and iOS.
date:
  created: 2026-09-30
  updated: 2026-09-30
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor Document Scanner: sdks/capacitor/document-scanner.md
  - Capacitor PDF Viewer: sdks/capacitor/pdf-viewer.md
  - Capacitor Printer: sdks/capacitor/printer.md
  - Capacitor File Opener: sdks/capacitor/file-opener.md
faq: true
---

# How to Scan a Document to PDF in a Capacitor App

A signed contract that comes out of a scanner as a folder of JPEGs is hard to email, print or file, so most apps want one PDF instead. The [Capacitor Document Scanner plugin](../../sdks/capacitor/document-scanner.md) returns that PDF next to the individual page images when you pass `generatePdf: true` to `scanDocument(...)`. This guide shows how to scan a document to PDF in a Capacitor app and what to do with the file afterwards, from opening it in a native viewer to printing it, sharing it and keeping it past the next app launch. The scanner and the Capacitor Printer plugin are part of [Capawesome Insiders](../../insiders/index.md); the PDF viewer, the file opener and the official Share and Filesystem plugins are free.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Ship a fix in one command with Capawesome Cloud Live Updates, no store review" src="https://capawesome.io/assets/banners/cloud-ship-a-fix-in-one-command.png" />
  </a>
</div>

**Key takeaways:**

- `scanDocument({ generatePdf: true })` returns one combined PDF in `pdf` and still returns every page as a JPEG in `scannedImages`, from the same scan.
- On Android, Google's ML Kit generates the PDF; on iOS, VisionKit returns only page images, and the plugin composes an image-only PDF from them.
- The `pdf` value is a file URL in the app's cache that the PDF Viewer, Printer, File Opener and `@capacitor/share` plugins accept without conversion.
- `pageLimit` defaults to 10 and caps the PDF as well, so raise it for longer documents; on iOS, extra pages are dropped after scanning.
- The Capacitor Document Scanner plugin clears its cache files when it loads on the next app launch, so copy the PDF to `Directory.Data` in the same flow.

## One PDF per scan

To turn scanned pages into a single PDF in Capacitor, call [`scanDocument(...)`](../../sdks/capacitor/document-scanner.md#scandocument) of the Capacitor Document Scanner plugin with `generatePdf: true`. The result then holds one combined PDF of all scanned pages in `pdf`, and the pages themselves as JPEG files in `scannedImages`. The plugin is part of Capawesome Insiders. To install it, please refer to the [Installation](../../sdks/capacitor/document-scanner.md/#installation) section in the plugin documentation, and do the same for each companion plugin below. [Announcing the Capacitor Document Scanner Plugin](./announcing-the-capacitor-document-scanner-plugin.md) covers setup and the Android scanner options, so this guide starts at the scan call:

```typescript
import { DocumentScanner } from '@capawesome-team/capacitor-document-scanner';

const scanToPdf = async () => {
  try {
    const { pdf, scannedImages } = await DocumentScanner.scanDocument({
      generatePdf: true,
    });
    return { pdf, scannedImages };
  } catch (error) {
    if ((error as { code?: string }).code === 'SCAN_CANCELED') {
      return null;
    }
    throw error;
  }
};
```

When the user backs out of the scanner, the promise rejects with the `SCAN_CANCELED` error code. That is a normal outcome, so the function returns `null` instead of throwing. `pdf` is typed `string | null` because it is only set when `generatePdf` is `true`; the snippets in the following sections take it as a `string`.

The two platforms produce the PDF differently. On Android, ML Kit generates it. Google's [Android guide](https://developers.google.com/ml-kit/vision/doc-scanner/android){:target="_blank"} states that the scanner can return both PDF and JPEG files for a scan, depending on the formats an app requests with `setResultFormats`, and it recommends requesting only the formats you need, since generating document files takes time and processing power. The scanner itself is a Google Play services module rather than part of your APK. Google lists its app size impact as a "~300KB download size increase", and it only runs on devices with Play services, which is what `isAvailable()` checks on Android.

On iOS, Apple's [`VNDocumentCameraViewController`](https://developer.apple.com/documentation/visionkit/vndocumentcameraviewcontroller){:target="_blank"} returns page images and no PDF. Apple leaves the export to the app: "With the collection of scanned images, your app can create a digital version of the physical document and export the scanned images to PDF." The plugin does that export with one PDF page per scanned page, and the result is an image-only PDF without a text layer.

## Page limit and quality

The `pageLimit` option caps the PDF along with the images. It defaults to `10`, so a 14-page contract never fits into one PDF with default options. Raise the limit for long documents:

```typescript
import { DocumentScanner } from '@capawesome-team/capacitor-document-scanner';

const scanContract = async () => {
  const { pdf } = await DocumentScanner.scanDocument({
    generatePdf: true,
    pageLimit: 30,
  });
  return pdf;
};
```

On Android, ML Kit enforces the limit inside the scanner UI. VisionKit has no public API to stop the scanner after a set number of pages, so on iOS the plugin truncates the result after scanning, and the pages past the limit are missing from the PDF as well. Set `pageLimit` above the longest document your users handle, or mention the limit in your own UI before the scanner opens.

The `imageQuality` option does not shrink the PDF. It re-encodes only the JPEG pages and leaves the PDF untouched on both platforms, and the plugin has no option for the PDF's page size, resolution or compression. Lower `imageQuality` when you store or upload the JPEGs, not to make `pdf` smaller.

## Open the PDF in-app

The [Capacitor PDF Viewer plugin](../../sdks/capacitor/pdf-viewer.md) shows the scanned PDF in a fullscreen native viewer with a toolbar, paging and pinch-to-zoom, without the user leaving your app. It is free. Pass the scanner's `pdf` value as `path` to [`open(...)`](../../sdks/capacitor/pdf-viewer.md#open):

```typescript
import { PdfViewer } from '@capawesome/capacitor-pdf-viewer';

const openScan = async (pdf: string) => {
  await PdfViewer.open({
    path: pdf,
    title: 'Rental contract',
    showShareButton: true,
  });
};
```

Always pass a `title`. The viewer falls back to the file name, and the scanner names its files with a generated ID, not with anything a user would recognize. `showShareButton` adds a share button to the viewer toolbar on Android and iOS; on Android, it depends on the same file provider setup as the Share plugin, which the sharing section below explains.

The viewer renders differently per platform. On iOS, the plugin uses Apple's PDFKit and needs no configuration. On Android, it uses the android-pdf-viewer library, which bundles the Pdfium native libraries and adds about 10 to 16 MB (uncompressed, across all ABIs) to your app. Published as an Android App Bundle, each device downloads only the libraries for its own ABI. The Android viewer does not support text selection.

## Print the PDF

The [Capacitor Printer plugin](../../sdks/capacitor/printer.md) is an Insiders plugin like the scanner. Its [`printPdf(...)`](../../sdks/capacitor/printer.md#printpdf) method presents the platform's printing UI for the scanned PDF on Android and iOS:

```typescript
import { Printer } from '@capawesome-team/capacitor-printer';

const printScan = async (pdf: string) => {
  await Printer.printPdf({
    name: 'Rental contract',
    path: pdf,
  });
};
```

`name` sets the print job name and defaults to `Document`, so pass the same label you gave the viewer. For a single page, the plugin prints the scanned images too, for example with `printFile(...)`. [Exploring the Capacitor Printer API](./exploring-the-capacitor-printer-api.md) covers the remaining print methods, from base64 data to HTML.

## Share or hand off

The free [`@capacitor/share`](https://capacitorjs.com/docs/apis/share){:target="_blank"} plugin from the Capacitor team opens the platform's share sheet, so the user can send the PDF to another app, such as a mail client or a messenger. Its `files` option takes an array of `file://` URLs on Android and iOS, and the scanner's `pdf` value is such a URL:

```typescript
import { Share } from '@capacitor/share';

const shareScan = async (pdf: string) => {
  await Share.share({
    title: 'Rental contract',
    files: [pdf],
  });
};
```

`title` becomes the subject when the user shares to email. If the user closes the sheet without picking an app, `share()` rejects with the message `Share canceled`, which deserves the same handling as a canceled scan.

Sharing the scanned PDF on Android needs no configuration. The Share documentation gives the reason:

> By default, Capacitor apps only allow to share files from caches folder. To make other Android folders shareable, they have to be added in `android/app/src/main/res/xml/file_paths.xml` file.

The scanner writes to that cache folder, and the `file_paths.xml` of a default Capacitor app already declares it with a `<cache-path>` entry. The PDF Viewer share button and the File Opener plugin pass files to other apps through the same file provider, so they work on the cached PDF without changes too.

To open the PDF in another app rather than send it, use the free [Capacitor File Opener plugin](../../sdks/capacitor/file-opener.md). [`openFile(...)`](../../sdks/capacitor/file-opener.md#openfile) determines the MIME type on its own:

```typescript
import { FileOpener } from '@capawesome-team/capacitor-file-opener';

const openInDefaultApp = async (pdf: string) => {
  await FileOpener.openFile({ path: pdf });
};
```

On iOS, the call shows a Quick Look preview inside your app, with the system share button. On Android, it opens the PDF in the user's default PDF app, or shows the app chooser if no default is set. Compared with the PDF Viewer plugin, File Opener leaves the choice of viewer to the platform, while the PDF Viewer plugin lets you set the title and the initial page and listen for page changes.

## Keep the PDF

The Capacitor Document Scanner plugin writes the PDF to the app's cache directory and deletes it again on the next app launch. From the plugin's FAQ:

> Stale files created by the plugin are cleaned up automatically when the plugin is loaded. Copy the files to a persistent location if you need to keep them.

The official [`@capacitor/filesystem`](https://capacitorjs.com/docs/apis/filesystem){:target="_blank"} plugin, which is free, copies the PDF to a persistent directory:

```typescript
import { Directory, Filesystem } from '@capacitor/filesystem';

const keepScan = async (pdf: string) => {
  const { uri } = await Filesystem.copy({
    from: pdf,
    to: 'contract.pdf',
    toDirectory: Directory.Data,
  });
  return uri;
};
```

Without a `directory` option, the Filesystem plugin reads `from` as a full `file://` path, and `toDirectory` places the copy in `Directory.Data`, which is the app's files directory on Android and the Documents directory on iOS. Files there are deleted when the app is uninstalled, not on the next launch. `copy(...)` resolves with the `uri` of the copy, which you store with the document record. Give every document its own file name, since a fixed name like `contract.pdf` only fits one.

The [Capacitor File Manager plugin](../../sdks/capacitor/file-manager.md), an Insiders plugin, does the same with `copyFile(...)`. The method copies natively with constant memory usage, and the plugin takes `file://` URIs throughout its API.

On iOS, the Filesystem documentation lists two `Info.plist` keys, `UIFileSharingEnabled` and `LSSupportsOpeningDocumentsInPlace`, that make the app's files appear in the Files app when set to `YES`. On Android, [Android Scoped Storage in Capacitor Apps, Explained](./android-scoped-storage-in-capacitor-apps.md#which-directory-to-use) compares the directories an app can write to without a permission.

Moving the PDF out of the cache has one consequence on Android. The app's files directory is not in Capacitor's default `file_paths.xml`, so the Share plugin, the File Opener plugin and the PDF Viewer share button cannot pass the copy to another app until you declare it. Add a `<files-path>` entry to `android/app/src/main/res/xml/file_paths.xml`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-path name="my_images" path="." />
  <cache-path name="my_cache_images" path="." />
  <files-path name="files" path="." />
</paths>
```

The first two entries are the Capacitor defaults, and `<files-path>` adds the app's files directory.

If the scan goes to a backend instead of staying on the device, the [Capacitor File Transfer plugin](../../sdks/capacitor/file-transfer.md), also an Insiders plugin, uploads files in the background. [Announcing the Capacitor File Transfer Plugin](./announcing-the-capacitor-file-transfer-plugin.md) walks through it.

## FAQ

### How do I turn scanned pages into a single PDF in Capacitor?

Call `scanDocument(...)` of the [Capacitor Document Scanner plugin](../../sdks/capacitor/document-scanner.md) with `generatePdf: true`. The result contains one combined PDF in `pdf` and the page images in `scannedImages`. ML Kit generates the PDF on Android, the plugin composes it from the VisionKit page images on iOS, and it holds at most `pageLimit` pages (default 10).

### Is the scanned PDF searchable?

The Capacitor Document Scanner plugin runs no OCR, and on iOS the PDF has no text layer. If you need the text of a document, use the free [Capacitor ML Kit Text Recognition plugin](../../sdks/capacitor/mlkit/text-recognition.md). Run its [`processImage(...)`](../../sdks/capacitor/mlkit/text-recognition.md#processimage) method on the JPEG pages in `scannedImages`.

### Does scan-to-PDF work on the web?

No. `scanDocument(...)` rejects with an unimplemented error on the web. The PDF Viewer plugin has no web implementation either, since browsers display a PDF in an `<iframe>` or `<object>` element. On the web, the Printer plugin only prints the current web view, and the File Opener plugin takes a `Blob` instead of a path.

### Where is the scanned PDF stored, and for how long?

In the app's cache directory, until the Capacitor Document Scanner plugin loads again on the next app launch and deletes its stale files. The Capacitor Filesystem documentation adds that the cache "can be deleted in cases of low memory". Copy the PDF to `Directory.Data` with `@capacitor/filesystem` to keep it.

### Can users sign or annotate the scanned PDF?

Yes, with the free [Capacitor PDF Annotator plugin](../../sdks/capacitor/pdf-annotator.md). Its [`open(...)`](../../sdks/capacitor/pdf-annotator.md#open) method shows a local PDF in a fullscreen native viewer with markup tools and resolves with the path of an annotated copy, leaving the original file unchanged. On iOS it uses Quick Look, whose tools include a signature. On Android it requires Android 11 or higher and a PDF system module that supports annotations, and Google marks those features as experimental.

### Is the Capacitor Document Scanner plugin free?

No. It is part of the [Capawesome Insiders](../../insiders/index.md) subscription, as is the Capacitor Printer plugin. The Capacitor PDF Viewer plugin, the Capacitor File Opener plugin, `@capacitor/share` and `@capacitor/filesystem` are free.

## Related posts

- [Announcing the Capacitor Document Scanner Plugin](./announcing-the-capacitor-document-scanner-plugin.md)
- [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md)
- [Exploring the Capacitor Printer API](./exploring-the-capacitor-printer-api.md)
- [Android Scoped Storage in Capacitor Apps, Explained](./android-scoped-storage-in-capacitor-apps.md)

## Try Capawesome Cloud

The scanner, the viewer, the printer and the file opener are native plugins, so adding them to an app takes a new native build rather than a web update. Capawesome Cloud builds and signs that app for Android and iOS without a Mac, and [How to Sign & Build Capacitor Apps in the Cloud](./how-to-sign-and-build-your-capacitor-app-in-the-cloud.md) shows the steps.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

Set `generatePdf: true` whenever the scan leaves your code as one document, whether it goes to a viewer, a printer, a share sheet or a backend, and leave it off when only the pages matter. Copy the PDF out of the cache in the same function that scanned it rather than on a later screen, because a crash and relaunch in between deletes it. Work with `scannedImages` when you need the pages one by one, for OCR or for edits such as brightness and contrast; [How to Take and Edit Photos in a Capacitor App](./how-to-take-and-edit-photos-in-a-capacitor-app.md) covers the image tools for that.

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
