SharePointVault ~/gotchas/spfx ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ previewing-library-files-in-your-app.md
sharepoint-vault›gotchas›spfx›previewing-library-files-in-your-app.mdhistoryedit on GitHub
● GOTCHAspfx· 6 min read

Previewing library files in your own app: every format behaves differently

applies to: SharePoint Online (SPFx web parts, document libraries)reviewed: 2026-11-12#spfx#sharepoint#files#preview#iframe#xss#svg

There is no single way to show a library file inside your app. PDFs embed straight from their URL, Office files need the WOPI frame keyed by UniqueId, and HTML cannot be embedded by URL at all — SharePoint serves it as an attachment, so the iframe stays blank with a clean console.

Neexistuje jeden způsob, jak zobrazit soubor z knihovny uvnitř appky. PDF se vloží přímo z URL, Office potřebuje WOPI frame klíčovaný přes UniqueId a HTML přes URL vložit nejde vůbec – SharePoint ho posílá jako attachment, takže iframe zůstane prázdný a konzole čistá.

And two that bite later. SVG is not an image for this purpose — it renders from a plain URL and can carry script, so framing it from your own tenant origin runs an uploader-controlled script in the viewer's session. Files in OneDrive (<tenant>-my.sharepoint.com) refuse framing altogether and leave a blank panel with nothing to catch.

A dvě, které koušou později. SVG pro tenhle účel není obrázek — z prosté URL se vykreslí a může nést skript, takže jeho vložením z vlastního tenanta spustíte skript nahrávajícího v session prohlížejícího. Soubory v OneDrivu (<tenant>-my.sharepoint.com) vkládání odmítají úplně a nechají prázdný panel, na kterém není co zachytit.

Symptom

You add a file preview dialog to your web part. Then:

  • PDF opens what looks like a folder listing instead of the document.
  • HTML shows an empty white frame. No error, no console message, nothing in the network tab that looks wrong — the request returns 200.
  • Images work fine, so the code "obviously" works.

Cause

Three separate behaviours that are easy to conflate:

  1. Doc.aspx only handles Office formats. Point it at a PDF and it falls back to rendering the library. Worse, if you pass a file URL as sourcedoc instead of the file's {UniqueId} GUID, it silently resolves to the containing library rather than erroring.
  2. PDFs need no viewer at all. SharePoint serves them as application/pdf with no Content-Disposition, so the browser's built-in viewer renders them from a plain <iframe src>.
  3. HTML from a document library is always sent as an attachment. That is deliberate — it stops anyone hosting web pages on your tenant. A browser will not render an attachment inside an iframe, so src produces an empty frame with no error to diagnose.

Diagnose in one request

Before guessing, read the headers. This answers all three questions at once:

const r = await fetch(fileUrl, { credentials: 'include' });
console.log(r.status, r.headers.get('content-type'), r.headers.get('content-disposition'));
// PDF  → 200  application/pdf   null          → embed directly
// HTML → 200  text/html         attachment;…  → src will NOT work

Fix — branch by format

// SVG is NOT in this list — see "Two more, learned the hard way" below.
if (isImage) return <img src={fileUrl} alt={name} />;

// PDF: direct — no WOPI, no Doc.aspx
if (isPdf) return <iframe title={name} src={fileUrl} />;

// Office: WOPI frame keyed by the file's UniqueId (a GUID), not its URL
if (isOffice) {
  const url = `${webUrl}/_layouts/15/WopiFrame.aspx?sourcedoc=%7B${uniqueId}%7D&action=embedview`;
  return <iframe title={name} src={url} />;
}

// HTML: fetch the markup yourself and inject it — this bypasses the attachment header.
// sandbox="" (no allow-scripts): styles render, foreign JS does not execute.
if (isHtml) return <iframe title={name} srcDoc={html} sandbox="" />;

return <DownloadFallback />;   // anything else: be honest, offer the download

Notes that save a second round of debugging:

  • Doc.aspx / WopiFrame.aspx are web-scoped. Build the URL from the file's web, not from the tenant root — otherwise you get "Item does not exist" for a file that plainly exists.
  • Fetching the HTML yourself is a privilege boundary decision. You are injecting content an editor uploaded into your authenticated page. Keep sandbox="" so styles work but scripts never run; do not "temporarily" add allow-scripts to make some flyer's animation work.
  • Always keep a download and an open-in-SharePoint action in the dialog header. Preview is a convenience; the file itself must stay reachable when the preview cannot render it.

Two more, learned the hard way

SVG renders — which is exactly the problem

.svg looks like an image, so it lands in the image branch and gets framed straight from its URL. That URL is on your tenant's origin, the same origin as the web part. An SVG can carry <script>, so anyone who can contribute to any library in your preview's reach can upload price-list.svg, wait for search to index it and for someone to click preview, and have their script run in that person's session under their identity. The same applies to .html if you "fixed" the blank frame by fetching the markup and injecting it without sandbox.

sandbox does not rescue the image branch: without allow-same-origin the request loses its cookies, so Office embeds and ordinary images stop loading too. Keep sandbox="" for the fetch-and-inject HTML path only.

Fix: exclude svg, html and htm from framing entirely and offer "Open in a new tab" instead — the same escape hatch you already need for OneDrive. .aspx can stay: modern SharePoint pages have their own pipeline, and a script uploaded as a file will not execute there.

const SCRIPTABLE = ['svg', 'html', 'htm'];      // never frame these from your own origin
if (SCRIPTABLE.indexOf(ext) !== -1) return <OpenInNewTabOnly url={fileUrl} />;

OneDrive refuses to be framed, and the iframe cannot tell you

Files under <tenant>-my.sharepoint.com ship stricter X-Frame-Options/CSP than team sites, so the preview ends as a blank panel — no error, no console message. You cannot measure it either: the frame is a foreign origin, so onLoad fires the same way whether it rendered or refused.

Fix: decide up front, from the host name, and show an explanation plus an "Open online" button instead of an empty box.

const isOneDrive = /-my\.sharepoint\.com$/i.test(new URL(fileUrl).host);

Same shape as the scriptable types: when you know framing will not work, say so — a blank panel reads as "your app is broken", not as "this file cannot be shown here".

?web=1 on a PDF denies access to people who can read the file

It is tempting to use fileUrl + '?web=1' for PDFs ("open in the SharePoint viewer"). For a user whose access is granted only on the file or its folder (unique permissions below the library, so on the library itself they have only Limited Access), that URL ends on _layouts/15/AccessDenied.aspx — for a document the same user can see in your list and download. (Our reading of why: the PDF viewer route needs access at the library level, which such a user does not have. We measured the outcome, not the internal route.)

Measured with such a user (read permission only on their own folder):

GET <file>.pdf                                → 200  application/pdf, no Content-Disposition, X-Frame-Options: SAMEORIGIN
GET <file>.pdf?web=1                          → redirect → _layouts/15/AccessDenied.aspx
GET _layouts/15/Doc.aspx?sourcedoc={id}&action=embedview  (a .docx in the same folder) → 200

Fix: frame PDFs by their plain URL (as above) — never ?web=1 — wherever your readers' access may be scoped to items or folders: personnel files, per-person folders, files shared with specific people. The same applies to an "Open in a new tab" button: for a PDF use the plain URL there too. Office files are fine through the WOPI frame.

Meta-lesson

Two of these three behaviours were already written down in our internal notes — and the code still shipped with Doc.aspx pointed at a PDF, because nobody thought to look under "REST writes" for something framed as "add a file preview". If your knowledge base is indexed by API, add symptom-shaped keywords (preview, show PDF, iframe, blank frame) so the note is findable from the task, not just from the API name.

Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-11-12last updated: 2026-11-12historyedit on GitHubview source