testiframe.com

How to Embed a PDF in HTML (Iframe, Object and Embed Compared)

Three HTML elements can show a PDF inside a page, and each relies on the browser's built-in viewer. Here's the code for each, when to pick which, and how to fix PDFs that download, stay blank or fall apart on phones.

Published · 10 min read
Quick answer

The simplest way to embed a PDF in HTML is an iframe: <iframe src="/files/guide.pdf" title="Product guide (PDF)" width="800" height="1000"></iframe>. Desktop browsers display it in their built-in viewer. Always add a direct link to the file, because many mobile browsers show only the first page or a download button. For identical rendering everywhere, use PDF.js.

Three ways to embed a PDF in HTML

None of these elements render the PDF themselves. They hand the file to the browser's built-in PDF viewer (Chrome and Edge have their own, Firefox uses PDF.js, Safari uses its own). What you see depends on that viewer, which is why the same markup can look different from one browser to the next.

1. The iframe element

<iframe
  src="/files/guide.pdf"
  title="Product guide (PDF)"
  width="800"
  height="1000"
  loading="lazy"
  style="border:0"></iframe>

The iframe treats the PDF like any other page. It gets an accessible title, supports loading="lazy", and follows the same framing rules as any embedded page. For the general attribute reference, see how to embed a website in HTML with an iframe.

2. The embed element

<embed
  src="/files/guide.pdf"
  type="application/pdf"
  width="800"
  height="1000">

<embed> is a void element: there's no closing tag and no place for fallback content. If the browser can't display the PDF, the visitor gets an empty box or a browser-supplied placeholder.

3. The object element

<object
  data="/files/guide.pdf"
  type="application/pdf"
  width="800"
  height="1000">
  <p>Your browser can't show this PDF here.
     <a href="/files/guide.pdf">Open the product guide (PDF, 1.2 MB)</a>.</p>
</object>

<object> renders its children when it can't display the resource. That built-in fallback is its main advantage.

Object vs embed vs iframe: which should you use?

<iframe><object><embed>
Fallback contentNo (children are ignored)Yes, children render if the PDF can'tNo (void element)
Accessible nametitle, well supportedtitle or aria-label; support is less consistentLess consistent
loading="lazy"YesNoNo
sandbox / allowYesNoNo
Your page's CSP directiveframe-srcobject-srcobject-src
PDF host's framing headers applyYesYesYes
Mobile behaviourSame for all three: it depends on the browser's viewer (see mobile limitations)

Use an iframe by default. It has the best accessibility support and lazy loading, and plenty of sites set object-src 'none' in their CSP as a hardening measure, which quietly blocks <object> and <embed>. Choose <object> only when you specifically want the fallback markup. There's no strong reason to use <embed> for PDFs today.

How to add fallback content for a PDF

Fallback matters because the viewer isn't always there: mobile browsers, locked-down corporate machines, and users who've set their browser to download PDFs instead of opening them. Two layers work well together.

First, always put a plain link next to the embed, whatever element you use. It's the one thing that works everywhere:

<figure class="pdf">
  <iframe src="/files/guide.pdf" title="Product guide (PDF)" loading="lazy"></iframe>
  <figcaption>
    <a href="/files/guide.pdf">Open the product guide (PDF, 1.2 MB)</a>
  </figcaption>
</figure>

<style>
  .pdf iframe { display:block; width:100%; height:80vh; border:0; }
</style>

Second, you can check navigator.pdfViewerEnabled, which browsers use to report whether they can show PDFs inline. If it's false, swap the frame for a link-only card:

if (!navigator.pdfViewerEnabled) {
  document.querySelectorAll('.pdf iframe').forEach(frame => frame.remove());
}
Treat pdfViewerEnabled as a hint

A browser can report true and still give a poor inline experience, as some mobile browsers do. Check current support on caniuse, and keep the visible link either way.

Why a PDF downloads or won't show in an iframe

When an embedded PDF fails, the cause is almost always a response header, either on the server hosting the PDF or on your own page.

Content-Disposition: attachment forces a download

If the server sends Content-Disposition: attachment, the browser downloads the file instead of displaying it, and the frame stays empty. A generic Content-Type such as application/octet-stream has the same effect. The PDF needs Content-Type: application/pdf and either no Content-Disposition or inline:

Content-Type: application/pdf
Content-Disposition: inline; filename="guide.pdf"

In Express, res.download() always sets attachment; use res.sendFile() or express.static for files you want to embed. On Amazon S3, the headers are object metadata you set at upload:

aws s3 cp guide.pdf s3://my-bucket/files/guide.pdf \
  --content-type application/pdf \
  --content-disposition inline

Framing headers on the PDF's host

X-Frame-Options and CSP frame-ancestors apply to PDFs exactly as they do to HTML pages. Many servers add X-Frame-Options: DENY or SAMEORIGIN to every response, so a PDF on files.example.com refuses to load on www.example.com. In Chrome you'll see the familiar "refused to connect" page. The refused-to-connect troubleshooting guide walks through the console messages.

The fix belongs on the PDF's server. X-Frame-Options can't name another origin (ALLOW-FROM is obsolete and ignored), so use frame-ancestors for the PDFs and drop X-Frame-Options for those files. Nginx:

location ~* \.pdf$ {
    add_header Content-Disposition "inline" always;
    add_header Content-Security-Policy "frame-ancestors 'self' https://www.example.com" always;
}

Nginx only inherits add_header directives from the parent level when the current block defines none, so this block drops any server-wide X-Frame-Options for PDFs. It also drops your other server-level headers, such as HSTS, so repeat any you need. Apache:

<FilesMatch "\.pdf$">
    Header set Content-Disposition "inline"
    Header always set Content-Security-Policy "frame-ancestors 'self' https://www.example.com"
    # Remove X-Frame-Options from whichever table it was set in
    Header unset X-Frame-Options
    Header always unset X-Frame-Options
</FilesMatch>

Express, with helmet or anything else setting X-Frame-Options globally:

app.use('/files', express.static('files', {
  setHeaders(res, filePath) {
    if (filePath.endsWith('.pdf')) {
      res.set('Content-Disposition', 'inline');
      res.set('Content-Security-Policy', "frame-ancestors 'self' https://www.example.com");
      res.removeHeader('X-Frame-Options');
    }
  }
}));

For every option and the edge cases, see X-Frame-Options vs CSP frame-ancestors.

Test your PDF's URL

Paste the PDF link to preview it in a frame and see whether its host's headers allow embedding.

Your own page's CSP, mixed content and sandbox

  • CSP on the embedding page. An iframe needs the PDF's origin in frame-src (or child-src, or default-src if neither is set). <object> and <embed> need it in object-src.
  • Mixed content. An http:// PDF on an https:// page is blocked. Serve the file over HTTPS.
  • Sandbox. If you add sandbox to the iframe, test in every browser you support; some built-in PDF viewers won't render inside a sandboxed frame.

How to embed a PDF hosted on another domain

Embedding a cross-origin PDF with iframe, object or embed doesn't need CORS. Only two things decide whether it works: the host's framing headers and its Content-Type and Content-Disposition. You can't change either from your page, so check them before you commit to a host. You can paste the link into the testiframe.com iframe tester or deep-link a check, such as this test of a sample PDF URL.

  • Your own cloud storage (S3, R2, Google Cloud Storage): set the content type and disposition on each object, and keep framing headers off the bucket or CDN for PDFs. S3 presigned URLs can also override the disposition per request with the response-content-disposition parameter.
  • Google Drive: the normal file link won't frame, but the preview URL https://drive.google.com/file/d/FILE_ID/preview is built for embedding. The file must be shared with anyone who has the link.
  • Google Docs viewer (docs.google.com/viewer?url=…&embedded=true): this is unofficial and undocumented, and it often shows a blank frame. Don't rely on it for anything important.
  • Someone else's site: if their server blocks framing or forces a download, link to the file instead. Re-hosting a copy is only an option if you have the right to do so.

PDF viewer URL parameters: #page, #zoom and #toolbar

Many viewers read instructions from the URL fragment, based on Adobe's PDF open parameters. Combine them with &:

<iframe src="/files/guide.pdf#page=4&zoom=125" title="Product guide, page 4 (PDF)"></iframe>
ParameterWhat it asks forCaveats
#page=4Open at page 4.The most widely supported parameter.
#zoom=125Zoom to 125%.Firefox's PDF.js also accepts values like page-width; others vary.
#toolbar=0Hide the viewer toolbar.Honoured by Chrome's viewer, not universal. It doesn't prevent downloading.
#view=FitHFit the page width.Patchy support outside Adobe's own software.
#search=pricingSearch for a word.Supported by PDF.js; not by every viewer.
#nameddest=introJump to a named destination in the PDF.The destination must exist in the file.
Parameters are hints, not settings

Chrome and Firefox's PDF.js support several of these, while Safari and mobile viewers support fewer. Test in your target browsers, and never use #toolbar=0 as protection: if the browser can display a PDF, the visitor already has the file.

Why embedded PDFs don't work well on mobile

This is where embedded PDFs let people down most. Mobile browsers, especially iOS Safari and Chrome on Android, often don't render inline PDFs well. Depending on the browser and version, visitors may see only the first page, a static image they can't scroll through, or just a button to open or download the file. No attribute or parameter changes that, and all three elements behave the same way.

Your practical options:

  • Make the link prominent on small screens. A clear "Open PDF" button often beats a cramped frame, because the phone's full-screen viewer works well once the file is opened directly.
  • Render with PDF.js (next section) if the document must display inline on phones.
  • Publish key content as HTML. A PDF menu or price list is usually better as a web page that simply links to the PDF.

If you do show a frame on phones, size it with CSS rather than fixed attributes. The responsive iframe guide covers aspect-ratio and viewport-based heights.

Using PDF.js for consistent PDF rendering

PDF.js is Mozilla's open-source PDF renderer, the same engine behind Firefox's viewer. It draws pages with JavaScript and canvas, so the result looks the same in every modern browser, including mobile ones. It's the most reliable choice when inline display really matters.

Option 1: host the prebuilt viewer

Download a release from the PDF.js project, put it on your server, and point an iframe at its viewer with the file as a parameter:

<iframe
  src="/pdfjs/web/viewer.html?file=%2Ffiles%2Fguide.pdf"
  title="Product guide (PDF)"
  style="border:0; width:100%; height:80vh"></iframe>

You get toolbar, search, zoom, thumbnails and a selectable text layer. By default the viewer refuses to open files from a different origin, so host the PDFs alongside it or adjust its configuration.

Option 2: render pages yourself

With the pdfjs-dist package you control the markup. This renders page 1 to a canvas:

import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString();

const pdf = await pdfjsLib.getDocument('/files/guide.pdf').promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.5 });

const canvas = document.querySelector('#pdf-page');
canvas.width = viewport.width;
canvas.height = viewport.height;

await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;

The API changes between major versions, so check the docs for the version you install. Two trade-offs to plan for: PDF.js fetches the file with JavaScript, so a PDF on another origin needs a CORS header such as Access-Control-Allow-Origin: https://www.example.com; and a bare canvas is invisible to screen readers, so either use the prebuilt viewer or add the text layer yourself.

How to make an embedded PDF accessible

  • Name the frame. Give the iframe a title that says what the document is and that it's a PDF, for example title="2026 price list (PDF)".
  • Always link to the file. Include the file type and size in the link text: "Download the 2026 price list (PDF, 1.2 MB)". Many assistive-technology users prefer opening the file in a dedicated reader.
  • Fix the PDF itself. Embedding doesn't make a document accessible. The PDF needs real text (not scanned images), tags, a logical reading order and alt text for images.
  • Offer HTML for important content. A web page is easier to read on every device, easier to search, and gives search engines content on your own URL. Google indexes a framed PDF at its own address rather than as part of your page; see how Google handles embedded content.
A solid default

Iframe with a descriptive title, loading="lazy" and CSS sizing, a visible link with file type and size underneath, and the PDF served as application/pdf with Content-Disposition: inline. Switch to PDF.js only when mobile inline display is a requirement.

FAQ

How do I embed a PDF in HTML?

Point an iframe at the file: <iframe src="/files/guide.pdf" title="Product guide (PDF)" width="800" height="1000"></iframe>. Desktop browsers show it in their built-in PDF viewer. Add a normal download link next to it for mobile browsers and anyone who can't use the viewer.

Why is my PDF not showing in the iframe?

The usual causes are an X-Frame-Options or CSP frame-ancestors header on the server hosting the PDF, a frame-src or object-src rule in your own page's CSP, an http:// PDF on an https:// page, or a mobile browser that has no inline PDF viewer. The browser console normally names the cause.

Why does my embedded PDF download instead of displaying?

The server is sending Content-Disposition: attachment, or a generic Content-Type such as application/octet-stream instead of application/pdf. Serve the file with Content-Type: application/pdf and Content-Disposition: inline.

Should I use object, embed or iframe for a PDF?

Use iframe in most cases: it supports title, loading="lazy" and sandbox, and it falls under your CSP frame-src. Use object when you want built-in fallback content. The embed element has no fallback and adds nothing the other two lack.

How do I embed a PDF so it works on mobile?

Built-in viewers on mobile often show only the first page or just a download link, so there is no markup that works everywhere. Render the PDF with PDF.js for consistent display, and always include a visible link that opens the file directly.

Can I stop people downloading an embedded PDF?

No. If a browser can display the PDF, it has already downloaded it. Parameters like #toolbar=0 only hide the viewer's buttons in some browsers. If the content must stay private, don't publish the file; use access controls instead.