Skip to main content
PDFBase
Developer Guides

How to Embed a PDF in a Web Page: 5 Methods Compared

PDFBase Team
July 26, 2026
9 min read

There are five ways to put a PDF on a web page, and the reason nobody agrees on which is best is that they fail in different places. Desktop browsers all ship a built-in PDF viewer, so every method looks fine on your machine. Mobile browsers mostly do not, and that is where the differences show up.

Here is what each method actually does, where each one breaks, and how to pick.

1. iframe

The most common answer, and a reasonable default.

<iframe
  src="/documents/invoice.pdf"
  title="Invoice 2026-04-118"
  width="100%"
  height="600"
></iframe>

The browser loads the PDF into a nested browsing context and hands it to whatever viewer it has. On desktop Chrome, Firefox, Edge and Safari that is a full viewer with page navigation, zoom, search and print.

Where it breaks: iOS Safari renders only the first page inside an iframe and does not scroll to the rest. This is long-standing behaviour, not a bug you can work around with CSS. Chrome for Android generally does not render PDFs inline at all and offers a download instead.

What it needs: a visible download link next to it, always. Not as a fallback inside the element — as a sibling the user can see.

<iframe src="/documents/invoice.pdf" title="Invoice" width="100%" height="600"></iframe>
<p><a href="/documents/invoice.pdf" download>Download the PDF (240 KB)</a></p>

2. object

<object data="/documents/invoice.pdf" type="application/pdf" width="100%" height="600">
  <p>
    Your browser cannot display this PDF.
    <a href="/documents/invoice.pdf" download>Download it instead</a>.
  </p>
</object>

object is the only one of these elements with real fallback semantics: the content between the tags is rendered when the browser cannot handle the type. That makes it the best choice when you want the fallback in the markup rather than bolted on beside it.

Where it breaks: the fallback fires only when the browser reports that it cannot handle application/pdf. A browser that says it can, then renders one page and stops, never shows your fallback. So iOS Safari still needs the sibling link.

3. embed

<embed src="/documents/invoice.pdf" type="application/pdf" width="100%" height="600" />

Shorter, void element, no fallback content at all. Behaviour is essentially identical to object minus the fallback. Use it when the markup is generated and you know a link sits next to it; use object otherwise. There is no rendering advantage to embed — the choice is purely about whether you want fallback children.

4. A Base64 data URI

When the PDF is not a file on your server — it came back from an API, it was generated in the browser, or the whole page has to work offline — you can inline it.

<embed
  src="data:application/pdf;base64,JVBERi0xLjcKJcfs..."
  type="application/pdf"
  width="100%"
  height="600"
/>

Our PDF to Base64 converter will produce that exact tag for you — pick the HTML embed, object, iframe or download-link output format and paste the result. It encodes in your browser, so a confidential document is never uploaded to generate the snippet.

What it costs. Base64 inflates the payload by a third: the encoded length is exactly ceil(bytes / 3) * 4. A 2 MB PDF becomes 2.7 MB of markup that is not cacheable separately from the page, cannot be range-requested, and has to be parsed as part of the HTML document. Inlining is right for a 40 KB one-page receipt and wrong for a 12 MB report.

Where it breaks: Chrome blocks top-level navigation to data: URLs, so a data URI works inside embed, object and iframe but a plain link to one may be blocked. For downloads, an anchor with a download attribute still works:

<a href="data:application/pdf;base64,JVBERi0..." download="invoice.pdf">Download invoice.pdf</a>

If the PDF is generated in the browser rather than fetched, prefer an object URL over a data URI. It avoids the 33% inflation entirely and the browser can stream it:

const url = URL.createObjectURL(pdfBlob)
iframe.src = url
// later, when the viewer is torn down:
URL.revokeObjectURL(url)

5. PDF.js

Rendering the PDF yourself with PDF.js is the only method that behaves identically everywhere, because it does not use the browser's viewer at all — it parses the file and paints pages to a canvas.

import * as pdfjsLib from 'pdfjs-dist'
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

const pdf = await pdfjsLib.getDocument({ url: '/documents/invoice.pdf' }).promise
const page = await pdf.getPage(1)
const viewport = page.getViewport({ scale: 2 })

const canvas = document.querySelector('#page')
canvas.width = viewport.width
canvas.height = viewport.height
await page.render({ canvasContext: canvas.getContext('2d'), viewport, canvas }).promise

What you get: consistent rendering on iOS, control over which pages appear, your own UI chrome, and the ability to render at a chosen DPI.

What it costs: roughly 300–400 KB of JavaScript plus a worker, and you now own the viewer. Page navigation, zoom, text selection, search, printing and accessibility are all yours to build. PDF.js ships a full viewer you can host, which is the pragmatic middle path.

Where it breaks: a canvas-rendered page contains no selectable text unless you also render the text layer, and no document structure for a screen reader. If accessibility matters — and for a public document it does — either render the text layer or make sure the download link gives people the real PDF.

Which one should you use?

  • A document people mostly want to read on desktop: object with fallback content, plus a visible download link.
  • A document people will mostly open on a phone: skip inline embedding. Show a card with the filename, size and page count, and a prominent download link. Every mobile browser handles a downloaded PDF well; none of them handle an embedded one well.
  • A PDF generated in the browser: an object URL in an iframe, revoked when you tear the view down.
  • A PDF that arrived as Base64 from an API: a data URI is fine below a megabyte or so; above that, decode to a Blob and use an object URL.
  • A viewer that must look and behave the same everywhere: PDF.js, and budget for the work.

The rule underneath all of it

Whatever you embed, always ship a real link to the file. Every method above has at least one mainstream browser where it degrades to a blank rectangle, and a blank rectangle with no link is a dead end. A download link costs one line and removes the entire failure class.

If you need to produce the markup for any of these, PDF to Base64 generates the embed, object, iframe, download-link and CSS url() forms directly from a file, entirely in your browser. Going the other way, Base64 to PDF turns an API response back into a file you can open.