Putting a PDF on a web page looks like a one-line job — and it is, if you accept whatever the visitor's browser decides to do with it. The differences between browsers, and between desktop and mobile, are where the work actually is. This guide covers the three embedding approaches, what each one costs, and how to make the result usable rather than merely present.
Option 1: just link to it
The simplest option, and the correct one more often than people admit:
<a href="/docs/annual-report.pdf">Annual report (PDF, 2.4 MB)</a>
Every browser handles this. The visitor gets their own viewer, with their own zoom settings, their own accessibility tooling, and the download button where they expect it. On mobile it opens in the platform viewer, which is better than any embed you will build.
Two courtesies that cost nothing and are almost always skipped:
- Say it is a PDF in the link text, not only in the URL. People plan differently for a PDF than a web page.
- Say how big it is. Someone on a metered connection needs to know before, not after.
If your only requirement is "the document should be available", stop here.
Option 2: the iframe
The pragmatic embed:
<iframe
src="/docs/annual-report.pdf#page=1&view=FitH"
width="100%"
height="800"
title="Annual report 2026">
</iframe>
This works in Chrome, Edge, Firefox, and Safari on the desktop, each rendering with its own built-in viewer. That means four slightly different toolbars, four different zoom behaviours, and four different sets of keyboard shortcuts — visually inconsistent, functionally fine.
The # fragment parameters are the useful, under-known part. They come from Adobe's open parameters specification and are honoured to varying degrees by browser viewers:
#page=7— open at page 7#view=Fit,#view=FitH,#view=FitV— fit page, width, height#zoom=150— open at 150%#toolbar=0— hide the toolbar (Chrome and Edge only, and not reliably)#search=invoice— open with a search term (Chrome, Edge)
Combine with &, as in the example. Support is patchy: treat these as hints that improve the experience where honoured, never as behaviour you depend on.
The iframe's real limitation is mobile. iOS Safari renders only the first page inside an iframe and does not scroll it, and various Android browsers refuse to render inline at all. Any responsible iframe embed needs a fallback.
Option 3: render it yourself with PDF.js
For full control — one consistent viewer everywhere, your own toolbar, no browser variation — you render the PDF into a canvas yourself. PDF.js is the standard way to do this, and it is what Firefox's own viewer is built from. It is also what browser-based PDF tools, including ours, use under the hood.
Two levels of effort:
- Ship the bundled viewer. PDF.js includes a complete viewer (
web/viewer.html) you can host and point an iframe at:<iframe src="/pdfjs/web/viewer.html?file=/docs/report.pdf">. Consistent everywhere, including iOS. Costs you roughly a megabyte of JavaScript. - Use the library directly. Load the document, render pages to canvases, build your own controls. This is the route if you want the document to feel like part of the page rather than a viewer bolted into it.
The trade-offs are real. You are shipping a large dependency, you are responsible for the accessibility of your custom viewer, and you have taken on rendering fidelity as your problem rather than the browser's. Do this when the document is central to the page — a product manual, a public dataset, a report people will genuinely read on the site — not to make a brochure look tidier.
Making the embed usable
Whichever route you pick, the same handful of things separate a good embed from a bad one.
Always provide a download link alongside. Even a perfect embed is worse than the native app for someone who wants to keep, annotate, or print the file. Put a plain link directly beneath.
Give the iframe a title. Screen readers announce it, and an untitled frame is announced as "frame", which tells the user nothing.
Do not rely on the embed for content that matters. An embedded PDF's text is invisible to your page's search, invisible to most site indexing, and awkward for assistive technology. If the content needs to be findable, publish it as HTML too — see PDF vs HTML for how to think about that choice.
Optimise the file for streaming. A linearised PDF lets the viewer show page one before the whole file has downloaded, which for a 40 MB document is the difference between "loading" and "broken". See linearized PDF and fast web view.
Compress it first. Web-embedded documents are routinely five times bigger than they need to be, almost always because of unnecessarily large images — reduce PDF file size covers what to cut.
Handle mobile explicitly. Either detect and swap the iframe for a link on narrow viewports, or use the PDF.js viewer which behaves consistently. Do not ship an iframe and hope.
Serving the file correctly
A few server-side details that cause confusing bugs:
Content-Type: application/pdf, or browsers may download rather than render. Files served from object storage with a generic type are the usual culprit.Content-Disposition: inlineto render in place;attachmentto force a download. If your embed shows a download prompt instead of the document, this header is why.X-Frame-Options/frame-ancestorsaffect embedding across origins. Serving the PDF from a CDN with restrictive framing headers will break the embed with an unhelpful error.- Range requests must be supported for linearisation to actually help. Most static hosts do this; some misconfigured proxies do not.
The accessibility question
An embedded PDF inherits the accessibility of the PDF itself, and most PDFs on the web are untagged, which means a screen reader gets an unstructured stream of text, or nothing at all. If you are publishing to the public — and especially if you have obligations under WCAG or an equivalent — the embed is not where you fix this. The document is.
That means tagging, reading order, alt text on images, and real table structure. The relevant ground is covered in PDF accessibility guide, reading order in tagged PDFs, and WCAG 2 for PDF accessibility. An untagged PDF in a beautiful custom viewer is still inaccessible.
Summary
Link to it if the document is a download. Use an iframe with fragment parameters and a mobile fallback if it should appear in the page. Ship PDF.js if the document is the page and consistency matters enough to justify the payload. In all three cases: serve the right content type, linearise and compress the file, keep a plain download link, and fix accessibility in the document rather than in the embed.