Rendering PDFs Without Surrendering the Page

Browser-native PDF viewing is consistent in exactly one way: every browser takes over the entire viewport. That works when a PDF is the destination, but it’s a poor fit when you want the document embedded in a larger application or page. The Adobe PDF Embed API is a free JavaScript library that keeps rendering inline, while also exposing UI configuration, annotations, events, and a post-render API.

Obtaining a Key

Before any code runs, you need credentials from Adobe’s Getting Started page. After creating an account, you’ll be asked for a credential name and an application domain. The latter is binding: each key is restricted to a single domain. For local development, use localhost; for CodePen experiments, use cdpn.io. If you need both local and production access, create separate credential sets in the console (multi-domain credentials are planned but not yet available).

Once credentials are created, the console also offers sample code and an interactive demo—useful for a quick look at the API’s capabilities before wiring up your own page.

Embedding a PDF in a Page

The first example is a minimal page with a styled <div> that will host the viewer. The SDK script is loaded via:

<script src="https://documentcloud.adobe.com/view-sdk/main.js"></script>

The library fires adobe_dc_view_sdk.ready when it loads, but depending on script loading order, that event may have already fired by the time you attach a listener. A robust check looks for window.AdobeDC and handles both cases by chaining to a setup function:

if (window.AdobeDC) displayPDF();
else {
  document.addEventListener("adobe_dc_view_sdk.ready", () => displayPDF());
}

function displayPDF() {
  console.log('Lets do some AWESOME PDF stuff!');
}

With the library ready, instantiate the viewer and call previewFile:

let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" });
adobeDCView.previewFile({
  content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
  metaData:{fileName: "cat.pdf"}
});

The constructor takes clientId (your API key) and divId (the ID of the container element, which needs an explicit CSS width and height). The first argument to previewFile is the PDF URL (CORS must be enabled for that resource), and the second is a metadata object containing the filename. The API also accepts File promises. The result is a full-featured viewer with familiar tools plus annotation support; a save icon exports the PDF with any comments and drawings included.

Four Embed Modes

A second options argument to previewFile controls the embedMode, which changes how the document is laid out:

  • Sized Container – the default; renders one page at a time inside the parent <div>.
  • Full Window – fills the container but lets you scroll through the entire document as one stream.
  • In-Line – stacks every page vertically inside the page. The container height is ignored, so the document expands the layout. Not ideal for large documents.
  • Lightbox – a centered modal with a dimmed backdrop and an automatic close control.

Switching modes is a one-line options change:

function displayPDF() {
  console.log('Lets do some AWESOME PDF stuff!');
  let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" });
  adobeDCView.previewFile({
    content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
    metaData:{fileName: "cat.pdf"}
  }, 
  {
    embedMode: "IN_LINE"
  });	
}

Lightbox mode is convenient when you want to defer loading until the user acts. The HTML drops the <div> (the modal handles the display), and a disabled button replaces it:

<html>
  <head></head>
  <body>
    <h1>Cats are Everything</h1>
    <p>
      Cats are so incredibly awesome that I feel like
      we should talk about them more. Here's a PDF
      that talks about how awesome cats are.
    </p>
		
    <!-- PDF here! -->
    <button id="showPDF" disabled>Show PDF</button>

    <p>
      Did you like that? Was it awesome? I think it was awesome! 
    </p>
  </body>
</html>

The JavaScript initializes without divId and instead enables the button on script load, so the user can trigger previewFile on click:

const ADOBE_KEY = 'b9151e8d6a0b4d798e0f8d7950efea91';

if(window.AdobeDC) enablePDF();
else {
  document.addEventListener("adobe_dc_view_sdk.ready", () => enablePDF());
}

function enablePDF() {
  let btn = document.querySelector('#showPDF');
  btn.addEventListener('click', () => displayPDF());
  btn.disabled = false;
}

function displayPDF() {
  console.log('Lets do some AWESOME PDF stuff!');
  let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY });
  adobeDCView.previewFile({
    content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
    metaData:{fileName: "cat.pdf"}
  }, 
  {
    embedMode: "LIGHT_BOX"
  });	
}

UI Customization

The viewer UI itself is configurable. The showAnnotationTools and showDownloadPDF flags, among others, can be toggled off to strip the interface down:

adobeDCView.previewFile({
	content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
	metaData:{fileName: "cat.pdf"}
}, 
{
	showDownloadPDF: false,
	showPrintPDF: false,
	showAnnotationTools: false,
	showLeftHandPanel: false
});	

With the defaults, the viewer shows the usual toolbar; disabling those options removes the corresponding controls entirely. It’s worth noting that hiding the download button doesn’t secure the file—the PDF URL remains visible in the page source.

Post-Render API and Events

previewFile returns a Promise, which gives access to details about the rendered document and the ability to listen for viewer events. Fetching metadata, for example, uses the returned API object:

let resultPromise = adobeDCView.previewFile({
  content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
  metaData:{fileName: "cat.pdf"}
}, { embedMode:"SIZED_CONTAINER" });	

resultPromise.then(adobeViewer => {
  adobeViewer.getAPIs().then(apis => {
    apis.getPDFMetadata()
    .then(result => console.log(result))
    .catch(error => console.log(error));
  });
});

That call returns basic document information such as page count:

{
  'numPages':6,
  'pdfTitle':'Microsoft Word - Document1',
  'fileName':''
}

Events can be combined with the API for custom analytics. The library emits events for actions like page views, and you can hook into those with your own handlers. One example tracks whether a user has viewed every page by recording each viewed page number in a Set, then comparing against the document’s total page count obtained through the API:

const ADOBE_KEY = 'b9151e8d6a0b4d798e0f8d7950efea91';

//used to track what we've read
const pagesRead = new Set([1]);
let totalPages, adobeDCView, shownAlert=false;

if(window.AdobeDC) displayPDF();
else {
  document.addEventListener("adobe_dc_view_sdk.ready", () => displayPDF());
}

function displayPDF() {
  console.log('Lets do some AWESOME PDF stuff!');
  adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" });
	
  let resultPromise = adobeDCView.previewFile({
    content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},
    metaData:{fileName: "cat.pdf"}
  }, { embedMode:"SIZED_CONTAINER" });	

  resultPromise.then(adobeViewer => {
    adobeViewer.getAPIs().then(apis => {
      apis.getPDFMetadata()
      .then(result => {
        totalPages = result.numPages;
        console.log('totalPages', totalPages);
        listenForReads();
      })
      .catch(error => console.log(error));
    });
  });
	
}

function listenForReads() {
	
  const eventOptions = {
    enablePDFAnalytics: true
  }

  adobeDCView.registerCallback(
  AdobeDC.View.Enum.CallbackType.EVENT_LISTENER,
  function(event) {
    let page = event.data.pageNumber;
    pagesRead.add(page);
    console.log(`view page ${page}`);
    if(pagesRead.size === totalPages && !shownAlert) {
      alert('You read it all!');
      shownAlert = true;
    }
  }, eventOptions
);

}

When the counts match for the demo document, an alert fires. The sample is contrived, but it demonstrates the pattern for wiring event-driven logic into the viewer.

Going Further

The full documentation covers UI options, event payloads, and Adobe Analytics integration. A live demo lets you try each mode and copies corresponding code. Support is available through Adobe’s community forum and the adobe-embed-api tag on Stack Overflow. For server-side PDF operations, Adobe also offers the PDF Tools API and Document Generation tools, which are separate from the free Embed API and available as a six-month trial.