PDF web server integration with cloud PDF viewer
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 callingload(). - 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 theserverUrlconfiguration 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.
| Scenario | serverUrl required? |
|---|---|
| Script served from Document Engine | No — automatically inferred |
| Script served from CDN | Yes |
| Script bundled in your application | Yes |
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/viewernpm install --save @nutrient-sdk/viewerAfter 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:
mkdir -p ./<your_projects_static_directory>/nutrient-webcp ./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/authfrom originhttps://your-app.comhas been blocked by CORS policy
To resolve the error, check the following settings:
- Verify that the JWT
allowed_originsclaim includes your frontend origin. - Match the origin’s protocol exactly (
httporhttps). - 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:
- Development — Use a development license that includes
localhost. - Production — Contact Support(opens in a new tab) to add your production domains to your license.
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:
| Feature | Standalone | Document Engine |
|---|---|---|
| Document source | URL, ArrayBuffer, or file | documentId on server |
| Annotation storage | Local or custom backend | Server-managed |
instantJSON parameter | Loads annotations from JSON | Ignores the parameter — Document Engine manages annotations |
autoSaveMode | Controls local saving | Controls syncing to Document Engine |
| Document streaming | Not available | Enabled 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 version | Incompatible Document Engine version | Affected functionality |
|---|---|---|
| 1.13–1.18 | Earlier than 1.12.0 | instance.exportPDF(). These Document Engine versions require legacy query-parameter authentication for /pdf, which these Web SDK releases don’t send. |
| 1.15 or later | Earlier than 1.16.0 | SearchType.WORD_BASED in server-backed mode. |
| 1.16 or later | Earlier than 1.16.0 | Removing password protection during server-backed PDF exports. |
| 1.16 or later | Earlier than 1.16.0 | Removing annotation notes after an XFDF roundtrip. |
| 1.17 or later | Earlier than 1.20.0 | Correct 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 later | Earlier than 1.18.0 | Document-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 later | Earlier than 1.18.0 | Annotation.createdBy with Instant synchronization in server-backed mode. |
| 1.21 or later | Earlier than 1.18.0 | overprintPreview in server-backed mode. |
| 1.21 or later | Earlier than 1.18.0 | blackRendering in server-backed mode. |
| 1.25 or later | Earlier than 1.20.0 | Viewing 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:
| Error | Cause | Solution |
|---|---|---|
container must be a valid element | Element doesn’t exist when you call load() | Use a framework lifecycle hook to wait for the container element |
Not licensed for origin | Domain not in license | Add the domain to your license. Refer to the domain configuration guide. |
| CORS errors | Cross-origin not configured | Add the allowed_origins claim to JWT |
| Connection timeout | Network or timeout configuration | Refer to the 504 response guide. |
| Authentication failed | Invalid or expired JWT | Regenerate 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.
Related guides
For additional troubleshooting and configuration instructions, refer to these guides: