---
title: "PDF.js: Access outline, bookmarks, and metadata"
canonical_url: "https://www.nutrient.io/blog/pdfjs-document-outline-bookmarks-metadata/"
md_url: "https://www.nutrient.io/blog/pdfjs-document-outline-bookmarks-metadata.md"
last_updated: "2026-07-28T17:19:31.445Z"
description: "Extract and render PDF document outlines, bookmarks, metadata, file attachments, and optional content layers with the PDF.js PDFDocumentProxy API."
---

**TL;DR**

- Pull the outline/bookmark tree with `pdfDocument.getOutline()` and navigate via `linkService.goToDestination()`.

- Read PDF metadata (title, author, version, form/signature flags) with `pdfDocument.getMetadata()` and Extensible Metadata Platform (XMP) via the returned `metadata` field.

- Access embedded files with `getAttachments()` and toggle layer visibility with `getOptionalContentConfig()`.

## Prerequisites

This guide assumes you have a loaded `PDFDocumentProxy` (the value `getDocument(...).promise` resolves to) and, for the navigation examples, a configured `PDFLinkService`. If you haven’t wired those up yet, start with our blog on [how to set up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md).

You’ll also need:

- `pdfjs-dist` 4.x or later

- A place to render a tree of React components alongside the page canvases (for the sidebar UI section)

## Document outline (table of contents)

The outline is the PDF’s bookmark tree — the table of contents that PDF readers show in a sidebar:

```tsx

const outline = await pdfDocument.getOutline();

```

### Outline structure

Each outline item has:

```ts

{
  title: "Chapter 1: Introduction",  // Display text.
  bold: false,                        // Bold styling.
  italic: false,                      // Italic styling.
  color: Uint8ClampedArray(3),        // RGB color [r, g, b].
  dest: [...],                        // Internal destination (page + position).
  url: null,                          // External URL (if it links outside).
  unsafeUrl: null,                    // Unsanitized version of `url`, before PDF.js escapes it.
  newWindow: false,                   // Whether an external URL should open in a new window.
  count: 3,                           // Number of child items.
  items: [...]                        // Nested child outline items.
}

```

### Rendering an outline sidebar

This component fetches the outline when the document loads and renders it as a nested list, navigating to a destination or opening an external URL when a reader clicks an item:

```tsx

function OutlineSidebar({ pdfDocument, linkService }) {
  const [outline, setOutline] = useState(null);

  useEffect(() => {
    pdfDocument.getOutline().then(setOutline);
  }, [pdfDocument]);

  if (!outline) return <div>No outline available</div>;

  return <OutlineTree items={outline} linkService={linkService} />;
}

function OutlineTree({ items, linkService, depth = 0 }) {
  return (
    <ul style={{ paddingLeft: depth * 16 }}>
      {items.map((item, i) => (
        <li key={i}>
          <button
            onClick={() => {
              if (item.dest) {
                linkService.goToDestination(item.dest);
              } else if (item.url) {
                window.open(item.url, "_blank");
              }
            }}
            style={{
              fontWeight: item.bold? "bold" : "normal",
              fontStyle: item.italic? "italic" : "normal",
            }}
          >
            {item.title}
          </button>
          {item.items?.length > 0 && (
            <OutlineTree
              items={item.items}
              linkService={linkService}
              depth={depth + 1}
            />
          )}
        </li>
      ))}
    </ul>
  );
}

```

### Navigating to destinations

Outline items contain `dest` arrays that you pass directly to `linkService.goToDestination()`:

```tsx

// Named destination (string).
linkService.goToDestination("chapter-1");

// Explicit destination (array).
linkService.goToDestination([
  pageRef,         // Page reference object { num, gen } from `getDestination()`.
                   // — Convert with `pdfDocument.getPageIndex(pageRef)` if you need an index.
  { name: "XYZ" }, // Type: XYZ, Fit, FitH, FitV, FitR, FitB.
  x,               // x position (or null).
  y,               // y position (or null).
  zoom,            // Zoom level (or null).
]);

```

### Resolving named destinations

Named destinations are strings that resolve to an explicit destination array. Look one up directly, or list every named destination the document defines:

```tsx

// Resolve a named destination to an explicit destination array.
const dest = await pdfDocument.getDestination("chapter-1");
// Returns: [pageRef, { name: "XYZ" }, x, y, zoom].

// Get all named destinations.
const destinations = await pdfDocument.getDestinations();

```

## Document metadata

PDF.js exposes document properties and XMP metadata through a single call:

```tsx

const metadata = await pdfDocument.getMetadata();

```

### Metadata structure

The returned object separates the legacy Document Information Dictionary from XMP metadata and a few document-level flags:

```ts

{
  info: {
    Title: "My Document",
    Author: "John Doe",
    Subject: "PDF Guide",
    Keywords: "pdf, javascript",
    Creator: "LaTeX",
    Producer: "pdfTeX-1.40.25",
    CreationDate: "D:20240115120000Z",
    ModDate: "D:20240115120000Z",
    PDFFormatVersion: "1.7",
    IsLinearized: false,
    IsAcroFormPresent: false,
    IsXFAPresent: false,
    IsCollectionPresent: false,
    IsSignaturesPresent: false,
  },
  metadata: {
    // XMP metadata (XML-based, may contain additional fields).
    // Access via `metadata.get("dc:title")`, `metadata.get("dc:creator")`, etc.
  },
  contentDispositionFilename: null,
  contentLength: 1234567,
  hasStructTree: true,  // True if the PDF has an accessibility structure tree.
}

```

### Using metadata

This component loads the `info` dictionary once the document is ready and renders the common fields in a definition list:

```tsx

function DocumentInfo({ pdfDocument }) {
  const [info, setInfo] = useState(null);

  useEffect(() => {
    pdfDocument.getMetadata().then(({ info }) => setInfo(info));
  }, [pdfDocument]);

  if (!info) return null;

  return (
    <div>
      <h3>Document Properties</h3>
      <dl>
        <dt>Title</dt><dd>{info.Title || "Untitled"}</dd>
        <dt>Author</dt><dd>{info.Author || "Unknown"}</dd>
        <dt>Creator</dt><dd>{info.Creator}</dd>
        <dt>Pages</dt><dd>{pdfDocument.numPages}</dd>
        <dt>PDF Version</dt><dd>{info.PDFFormatVersion}</dd>
        <dt>Has Forms</dt><dd>{info.IsAcroFormPresent? "Yes" : "No"}</dd>
      </dl>
    </div>
  );
}

```

## File attachments

Some PDFs contain embedded files (spreadsheets, images, other PDFs):

```tsx

const attachments = await pdfDocument.getAttachments();

```

### Attachments structure

Attachments come back as an object keyed by file name, with each entry holding the raw bytes and an optional description:

```ts

{
  "report.xlsx": {
    filename: "report.xlsx",
    content: Uint8Array(...),  // Raw file data.
    description: "Quarterly report data",
  },
  "photo.jpg": {
    filename: "photo.jpg",
    content: Uint8Array(...),
  },
}

```

### Downloading attachments

This component lists each attachment and wraps its bytes in a `Blob` so the browser can download it when a reader clicks it:

```tsx

function AttachmentList({ pdfDocument }) {
  const [attachments, setAttachments] = useState(null);

  useEffect(() => {
    pdfDocument.getAttachments().then(setAttachments);
  }, [pdfDocument]);

  if (!attachments) return <div>No attachments</div>;

  const downloadAttachment = (attachment) => {
    const blob = new Blob([attachment.content]);
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = attachment.filename;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <ul>
      {Object.entries(attachments).map(([name, attachment]) => (
        <li key={name}>
          <button onClick={() => downloadAttachment(attachment)}>
            {attachment.filename}
          </button>
          {attachment.description && <span> — {attachment.description}</span>}
        </li>
      ))}
    </ul>
  );
}

```

## Optional content groups (layers)

PDFs can contain toggleable layers, which are common in computer-aided design (CAD) drawings, maps, and technical documents:

```tsx

const optionalContentConfig = await pdfDocument.getOptionalContentConfig();

// List all groups.
const groups = optionalContentConfig.getGroups();
// { "layer-id-1": { name: "Dimensions", visible: true },... }

// Toggle a layer.
optionalContentConfig.setVisibility("layer-id-1", false);

// Rerender affected pages after toggling.
viewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);

```

## Key points

- `getOutline()` returns the full bookmark tree — render it as a sidebar for navigation

- `getMetadata()` returns both an `info` dict and XMP metadata

- `getAttachments()` returns embedded files as `Uint8Array` content — ready to download

- Use `linkService.goToDestination()` to navigate to outline destinations

- Optional content groups let you toggle visibility of PDF layers

- All these APIs are on `PDFDocumentProxy` — call them after the document loads

- These features are entirely built into PDF.js — no extra dependencies needed

## How Nutrient Web SDK handles this

Nutrient Web SDK ships an interactive sidebar with bookmark, thumbnail, and annotation views — toggling between them is one `setViewState` call. Metadata is exposed through a typed `getDocumentInfo()` method, so you don’t need to walk a free-form `info` dictionary:

```js

instance.setViewState((v) =>
  v.set("sidebarMode", NutrientViewer.SidebarMode.BOOKMARKS),
);

const documentInfo = await instance.getDocumentInfo();
console.log(documentInfo.title, documentInfo.author);

```

That replaces the custom outline tree, sidebar UI, and `linkService.goToDestination()` wiring with two API calls. For navigation between pages and bookmark targets, see the related guides on [thumbnail sidebars](https://www.nutrient.io/blog/pdfjs-thumbnail-sidebar.md) and [navigation, zoom, and rotation](https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation.md).

[Learn more about Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) | [Migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) | [Contact Sales](https://www.nutrient.io/contact-sales/?=sdk)

## FAQ

#### What’s the difference between an outline and a bookmark?

In PDF terminology they’re the same thing — the PDF spec calls it an “outline,” but every PDF reader UI labels it “bookmarks.” `pdfDocument.getOutline()` returns the same tree that Acrobat shows in its bookmarks panel.

#### Why does <code>getOutline()</code> return <code>null</code> sometimes?

Because not every PDF has bookmarks. `getOutline()` returns `null` (not an empty array) when the document has no outline dictionary. Branch on `outline === null` in your UI to show a “no outline available” state rather than rendering an empty list.

#### What’s in <code>metadata.metadata</code> vs. <code>metadata.info</code>?

`info` is the legacy Document Information Dictionary — flat key-value pairs like `Title`, `Author`, `Producer`. `metadata` is an XMP packet, an Extensible Markup Language (XML) document that can carry arbitrary structured metadata (rights, history, custom schemas). Read XMP values with `metadata.get("dc:title")` or `metadata.getAll()`. Many PDFs have both; modern producers prefer XMP.

#### Can I read metadata from a password-protected PDF?

Only after the document loads, which requires the password. PDF.js’s `getDocument({ url, password })` decrypts the file before exposing any API; once `loadingTask.promise` resolves, all the methods on this page work normally.

#### Do typical PDFs have optional content groups (layers)?

Rarely. Layers show up in CAD exports, geographic information system (GIS) maps, multi-language documents, and some technical drawings. For a typical business PDF — invoices, reports, contracts — `getOptionalContentConfig().getGroups()` returns an empty object. It’s worth handling, but don’t expect to find layers in most files.

#### What format are file attachments in?

Whatever format the original file was. `getAttachments()` returns a `Uint8Array` of raw bytes per attachment — the original file could be `.xlsx`, `.docx`, `.jpg`, another `.pdf`, or anything else. The `filename` field gives you the original name with its extension, and the browser’s `Blob` API can wrap the bytes for download or further processing.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [or](/blog/sample-blog-updated.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

