This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/document-engine/viewer/client-integration/web.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. PDF web server integration with cloud PDF viewer | Nutrient

To load a document from Document Engine in Nutrient Web SDK, call NutrientViewer.load() with a configuration object:

NutrientViewer.load({
container: "#nutrient-container",
documentId: "<document_id>",
authPayload: { jwt: "<jwt>" },
instant: true,
serverUrl: "https://<your_document_engine_instance>/"
});

The configuration options are:

  • Container (container) — A CSS selector, such as "#nutrient-container", or a DOM element where you mount the viewer. Create the element before calling load().
  • Document identifier (documentId) — The identifier of an existing document on Document Engine. For upload instructions, refer to the API reference.
  • Authentication payload (authPayload) — An object containing the JSON Web Token (JWT) for authentication. Refer to the generating a JWT guide.
  • Instant collaboration (instant) — Whether to enable real-time collaboration with Nutrient Instant.
  • Server URL (serverUrl) — The URL of your Document Engine instance. Refer to the serverUrl configuration section.

Understanding serverUrl

The serverUrl configuration tells the Web SDK where to find Document Engine.

Web SDK infers the Document Engine URL from the base URL of the nutrient-viewer.js script tag. If you serve the script from a content delivery network (CDN) or bundle it with your application, set serverUrl.

ScenarioserverUrl required?
Script served from Document EngineNo — automatically inferred
Script served from CDNYes
Script bundled in your applicationYes

When Document Engine serves the script, omit serverUrl:

<script src="https://document-engine.example.com/nutrient-viewer.js"></script>
<script>
NutrientViewer.load({
container: "#viewer",
documentId: "abc123",
authPayload: { jwt: "..." }
// `serverUrl` is inferred from the script location.
});
</script>

When a CDN serves the script, specify serverUrl:

<script src="https://cdn.cloud.nutrient.io/pspdfkit-web@1.25.0/nutrient-viewer.js"></script>
<script>
NutrientViewer.load({
container: "#viewer",
documentId: "abc123",
authPayload: { jwt: "..." },
serverUrl: "https://document-engine.example.com/"
});
</script>

How to serve the Web SDK

Serve Nutrient Web SDK from Document Engine, the Nutrient CDN, or your application.

These options describe how to deliver the Web SDK JavaScript bundle, not how to store or process documents.

DWS Viewer API uses the same Web SDK bundle with DWS session authentication. It loads the bundle from the CDN or your application’s assets. For DWS integration instructions, refer to the DWS Viewer API getting started guide.

For a standalone setup with a publishable key, refer to the Web SDK quick start guide.

The option to serve from Document Engine applies only to self-hosted Document Engine. DWS Viewer API doesn’t proxy the Web SDK bundle through Document Engine.

Serving from Document Engine

To load Nutrient Web SDK from Document Engine, add the nutrient-viewer.js script to your application:

<script src="https://<document_engine_url>/nutrient-viewer.js"></script>

Document Engine proxies these requests to the Nutrient CDN(opens in a new tab).

Use another serving option in either situation:

  • You can’t provide CDN access. Document Engine requires CDN access to proxy the files.
  • You require a newer Web SDK version. Document Engine serves the Web SDK version available when that Document Engine version shipped.

Serving from CDN

The Nutrient CDN(opens in a new tab) hosts the Web SDK bundle, so your application can load the bundle directly instead of requesting it through Document Engine.

To load Nutrient Web SDK from the CDN, add the nutrient-viewer.js script to your application:

<script src="https://cdn.cloud.nutrient.io/pspdfkit-web@1.25.0/nutrient-viewer.js"></script>

Serving manually

Serve Web SDK from your application to load the bundle without CDN access, including when you’re offline.

Install the Nutrient Web SDK npm package(opens in a new tab) with a package manager:

yarn add @nutrient-sdk/viewer

After installation, serve nutrient-viewer.js from node_modules/@nutrient-sdk/viewer/dist/ to your frontend.

Serve the nutrient-viewer-lib/ folder from the assets archive for the same version as the npm package, not from node_modules. The viewer loads files from this folder in server-backed mode too. Since Nutrient Web SDK 1.9.1, loading the folder from the npm package shows the Copying assets from the npm package is deprecated warning in the console.

For example, add both to your application’s static assets. Then load the script from that location:

<script src="https://<your_app_url>/static/nutrient-web/nutrient-viewer.js"></script>

To copy the entry script to your project’s static directory, use an npm prepare script(opens in a new tab):

"scripts": {
...
"prepare": "mkdir -p ./<your_projects_static_directory>/nutrient-web && cp ./node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer.js ./<your_projects_static_directory>/nutrient-web/"
},

Then run these commands from your project’s root. They copy nutrient-viewer.js and extract the assets archive for the installed version into the same nutrient-web directory, so both come from the same version:

Terminal window
mkdir -p ./<your_projects_static_directory>/nutrient-web
cp ./node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer.js ./<your_projects_static_directory>/nutrient-web/
VERSION=$(node -p "require('@nutrient-sdk/viewer/package.json').version")
curl -fLo nutrient-assets.zip "https://cdn.cloud.nutrient.io/assets/pspdfkit-web-assets@${VERSION}.zip"
unzip -o nutrient-assets.zip -d ./<your_projects_static_directory>/nutrient-web/

Run these commands again whenever you update the npm package. When you call NutrientViewer.load(), set baseUrl to the absolute URL of the nutrient-web directory, including the trailing slash — for example, https://<your_app_url>/static/nutrient-web/.

Replace <your_projects_static_directory> with your project’s directory for static files.

Framework integration

Use your framework’s lifecycle hooks to load and unload Web SDK with Document Engine.

React

This React component loads the viewer when the document, JWT, or server URL changes, and its cleanup function unloads the viewer:

import { useEffect, useRef } from "react";
function PDFViewer({ documentId, jwt, serverUrl }) {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
let instance;
(async () => {
const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({
container,
documentId,
authPayload: { jwt },
serverUrl,
});
})();
return () => instance?.unload();
}, [documentId, jwt, serverUrl]);
return <div ref={containerRef} style={{ height: "100vh" }} />;
}

Vue 3

This Vue component loads the viewer when Vue mounts the component and unloads the viewer when Vue unmounts the component:

<template>
<div ref="container" style="height: 100vh"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const props = defineProps(["documentId", "jwt", "serverUrl"]);
const container = ref(null);
let instance = null;
onMounted(async () => {
const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({
container: container.value,
documentId: props.documentId,
authPayload: { jwt: props.jwt },
serverUrl: props.serverUrl,
});
});
onUnmounted(() => instance?.unload());
</script>

Angular

This Angular component loads the viewer after Angular initializes the view and unloads the viewer when Angular destroys the component:

import {
Component,
ElementRef,
ViewChild,
AfterViewInit,
OnDestroy,
Input,
} from "@angular/core";
@Component({
selector: "pdf-viewer",
template: '<div #container style="height: 100vh"></div>',
})
export class PdfViewerComponent implements AfterViewInit, OnDestroy {
@ViewChild("container") container!: ElementRef;
@Input() documentId!: string;
@Input() jwt!: string;
@Input() serverUrl!: string;
private instance: any;
async ngAfterViewInit() {
const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
// Note: `ViewChild` is only available after view initialization.
this.instance = await NutrientViewer.load({
container: this.container.nativeElement,
documentId: this.documentId,
authPayload: { jwt: this.jwt },
serverUrl: this.serverUrl,
});
}
ngOnDestroy() {
this.instance?.unload();
}
}

Container timing — Call NutrientViewer.load() after your framework creates the container element. Use the appropriate lifecycle hook:

  • Angular — ngAfterViewInit
  • React — useEffect
  • Vue — onMounted

Cross-origin configuration (CORS)

If your application and Document Engine use different domains, configure cross-origin resource sharing (CORS) through the JWT allowed_origins claim.

Configuring allowed origins

Document Engine uses the JWT allowed_origins claim to identify origins that can send requests.

For token generation instructions, refer to the generating a JWT guide. Include your frontend origins in allowed_origins:

{
"document_id": "abc123",
"permissions": ["read-document"],
"allowed_origins": ["https://your-app.com", "https://staging.your-app.com"]
}

Setting allowed_origins to "any" enables requests from any origin. Don’t use this setting in production.

Common CORS errors

A CORS error can include this message:

Access to fetch at https://document-engine.example.com/i/d/5/auth from origin https://your-app.com has been blocked by CORS policy

To resolve the error, check the following settings:

  • Verify that the JWT allowed_origins claim includes your frontend origin.
  • Match the origin’s protocol exactly (http or https).
  • Include the port if your application uses a nonstandard port.

For details about allowed_origins, refer to the JWT authentication guide.

License and domain configuration

Document Engine validates that requests originate from licensed domains.

Origin validation errors

Document Engine reports this error when your license configuration doesn’t include the requesting origin:

PSPDFKit Document Engine is not licensed for use from the origin http://localhost:8080

To resolve the error, use the option for your environment:

For details, refer to the domain configuration guide.

Differences from standalone mode

Web SDK handles documents, annotations, and saving differently when you use Document Engine instead of standalone mode:

FeatureStandaloneDocument Engine
Document sourceURL, ArrayBuffer, or filedocumentId on server
Annotation storageLocal or custom backendServer-managed
instantJSON parameterLoads annotations from JSONIgnores the parameter — Document Engine manages annotations
autoSaveModeControls local savingControls syncing to Document Engine
Document streamingNot availableEnabled by default

Performance considerations

Use preload or prefetch to control when the browser downloads nutrient-viewer.js:

  • Preload the script on pages that display PDF content.
  • Prefetch the script on pages without visible PDF content when you expect users to request it later.

Preloading starts the download earlier in the page lifecycle. Prefetching downloads resources for future requests. For details, refer to the HTML specification(opens in a new tab).

The implementation depends on how your application imports nutrient-viewer.js.

For a website using vanilla JavaScript, add the appropriate tag to the page’s <head>:

  • Use <link rel="prefetch" href="path/to/nutrient-viewer.js"> to prefetch the script.
  • Use <link rel="preload" href="path/to/nutrient-viewer.js"> to preload the script.

For applications using webpack, add /* webpackPrefetch: true */ or /* webpackPreload: true */ inside import(). Each comment makes webpack generate the corresponding <link> tag. For details, refer to the webpack documentation(opens in a new tab).

Version compatibility

Document Engine and Web SDK versions are generally compatible in server-backed mode. Some Web SDK features require a minimum Document Engine version.

The following table lists known version requirements for specific features. It doesn’t cover every combination for current releases, so check the release notes for the versions you plan to deploy.

Web SDK versionIncompatible Document Engine versionAffected functionality
1.13–1.18Earlier than 1.12.0instance.exportPDF(). These Document Engine versions require legacy query-parameter authentication for /pdf, which these Web SDK releases don’t send.
1.15 or laterEarlier than 1.16.0SearchType.WORD_BASED in server-backed mode.
1.16 or laterEarlier than 1.16.0Removing password protection during server-backed PDF exports.
1.16 or laterEarlier than 1.16.0Removing annotation notes after an XFDF roundtrip.
1.17 or laterEarlier than 1.20.0Correct text selection highlights in certain PDFs after optical character recognition (OCR). Document Engine provides the selectable text. Until you upgrade Document Engine, the highlight skips spaces between words.
1.19 or laterEarlier than 1.18.0Document-defined annotation tab order in server-backed mode. Web SDK doesn’t request the unsupported endpoint and retains fallback tab ordering. instance.getPageTabOrder() reports that the Document Engine version doesn’t provide the document-defined order.
1.21 or laterEarlier than 1.18.0Annotation.createdBy with Instant synchronization in server-backed mode.
1.21 or laterEarlier than 1.18.0overprintPreview in server-backed mode.
1.21 or laterEarlier than 1.18.0blackRendering in server-backed mode.
1.25 or laterEarlier than 1.20.0Viewing and toggling PDF layers in server-backed mode. Web SDK hides the layers toolbar button. A layers sidebar opened through ViewState keeps showing a loading indicator. instance.getLayersVisibilityState() and instance.setLayersVisibilityState() reject with an error that names the required Document Engine version.

Document Engine serves the Web SDK version that shipped with its release. If you use the Nutrient CDN or bundle Web SDK with your application, check the version requirements above.

Troubleshooting

For integration issues not listed here, refer to the related guides below or contact Support(opens in a new tab).

Common errors

Use the following table to identify the cause of an error and its solution:

ErrorCauseSolution
container must be a valid elementElement doesn’t exist when you call load()Use a framework lifecycle hook to wait for the container element
Not licensed for originDomain not in licenseAdd the domain to your license. Refer to the domain configuration guide.
CORS errorsCross-origin not configuredAdd the allowed_origins claim to JWT
Connection timeoutNetwork or timeout configurationRefer to the 504 response guide.
Authentication failedInvalid or expired JWTRegenerate the JWT. Verify its claims, including exp. For expired tokens in long-running sessions, refresh authentication with onAuthFailed and setSession.

To renew JWTs without recreating the viewer, refer to the web client authentication and session renewal guide.

For additional troubleshooting and configuration instructions, refer to these guides: