All articles

Tutorial

How to Render and View PDF Files Inside an Android TWA

September 19, 2026 · 8 min read

A common pain point for developers converting their Progressive Web Apps into Android apps via a Trusted Web Activity (TWA) is handling PDF documents. On desktop browsers like Chrome or Edge, clicking a link to a PDF opens the document directly inside the browser window using a native, built-in PDF viewer. However, Chrome for Android does not include this native PDF engine. Consequently, when a user clicks a PDF link inside your Android TWA, the browser engine's default action is to trigger an external file download or attempt to open the PDF in an external application. This breaks the immersive app experience.

Why Inline PDF Rendering is Blocked by Default

In a standard web environment, developers rarely think about PDF rendering because the desktop ecosystem abstracts it away. When you launch a TWA, it is bound by the limitations of the mobile version of Chromium. Because storage space and performance are at a premium on mobile devices, Google stripped the heavy, proprietary PDFium engine from Chrome for Android. Without this rendering engine, a TWA has no default path to draw the canvas representation of a PDF on screen. Therefore, if your PWA expects to show invoices, manuals, tickets, or reports inline, you must implement a custom rendering strategy.

Strategy 1: Rendering PDFs Inline with PDF.js

The most robust, privacy-respecting, and offline-compatible method to handle PDF rendering inside a TWA is to use Mozilla's open-source library, PDF.js. This library parses and renders PDF files directly into an HTML5 Canvas, operating entirely within the standard sandbox of your Progressive Web App. Because it is purely JavaScript, it operates flawlessly inside a TWA.

To set this up, you must pull PDF.js into your web application project. The basic workflow involves fetching the PDF file as a binary stream, parsing it with PDF.js, and drawing each page on a canvas element within your DOM.

const pdfjsLib = window['pdfjs-dist/build/pdf']; pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js'; const loadingTask = pdfjsLib.getDocument('https://example.com/document.pdf'); loadingTask.promise.then((pdf) => { console.log('PDF loaded'); pdf.getPage(1).then((page) => { console.log('Page 1 loaded'); const scale = 1.5; const viewport = page.getViewport({ scale: scale }); const canvas = document.getElementById('pdf-canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; const renderContext = { canvasContext: context, viewport: viewport }; page.render(renderContext); }); });

By wrapping this implementation inside a dynamic route (e.g., /viewer?file=document.pdf), you can create an elegant, immersive, and custom-branded PDF viewing interface directly within your PWA that persists and functions even when the user is offline.

Comparing PDF Viewing Options in a TWA

Depending on your application's requirements, you can choose from several integration paths. Below is a detailed evaluation of each strategy:

StrategyOffline SupportUser ExperienceImplementation EffortPrivacy Level
PDF.js IntegrationExcellent (Cached via Service Worker)Seamless (Keeps user inside app)MediumHigh (Runs entirely on client)
Google Docs Viewer iframeNone (Requires internet access)Average (Includes Google branding)Low (Simple iframe src wrapper)Low (Sends document URLs to Google)
System Intent / DownloadGood (Opens in device's native app)Disruptive (Forces user out of your app)None (Default behavior)High (Uses local device viewer)

Strategy 2: The Google Docs Viewer Fallback

If you need a quick solution and do not require offline capability or strict privacy compliance for internal documents, you can use the Google Docs Viewer service within an iframe. This service acts as an intermediary, pulling the public PDF, parsing it on Google's servers, and returning an HTML-based rendering. To execute this, you load an iframe with a formatted URL pointing to the service.

The source of your iframe would be structured like this: https://docs.google.com/viewer?url=https://example.com/document.pdf&embedded=true.

While this is simple to drop into your code, it has major drawbacks. It will fail for files stored behind an authentication wall, it does not support offline viewing, and sending sensitive documents to a third-party server may violate enterprise security compliance policies. Therefore, it is generally discouraged for internal SaaS operations and accounting applications.

Configuring Proper Server Headers for Files

If you prefer to let users download PDFs directly to their Android devices rather than attempting to render them, you must ensure that your backend server serves the files with the correct HTTP headers. If the headers are configured incorrectly, the TWA engine may stall or exhibit unexpected behavior when a PDF is selected.

To trigger a native, clean download process in Android, the file must be served with the Content-Disposition header set to attachment, along with the correct MIME type. Ensure your server sends the following headers:

Content-Type: application/pdf Content-Disposition: attachment; filename="document.pdf"

When these headers are parsed by Chromium inside the TWA wrapper, Android will immediately intercept the stream, initiate a system download, and display a progress notification in the Android system tray. This prevents your web view from freezing or displaying a blank screen.

Managing Local Storage and Service Worker Caching for PDFs

When implementing an inline viewer with PDF.js, you should also leverage your PWA's Service Worker to cache frequently accessed documents. This is particularly useful for apps delivering tickets, offline product manuals, or contract templates. Ensure that your Service Worker is configured to cache files matching the PDF extension in your cache storage strategy. This ensures that even in areas with zero network connectivity, users can tap and read their critical documents inside your Play Store app without disruption.

Ready to ship your Android app?

Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.

Build my app