How to build a TypeScript PDF viewer with PDF.js
This article was first published in September 2021 and was updated in October 2024.
In this tutorial, we’ll show how to build a TypeScript PDF viewer with PDF.js, one of the most popular open source libraries for rendering PDF files in the browser.
Developers like TypeScript due to its type safety and the fact that it’s a superset of JavaScript. TypeScript adds static typing to JavaScript, which allows developers to catch errors at compile time instead of run time.
In the first part of this tutorial, we’ll walk through how to render a PDF in the browser with PDF.js and TypeScript. In the second part, we’ll look at how to build a fully featured PDF viewer with the Nutrient TypeScript PDF library. Our PDF viewer library provides some additional benefits beyond what PDF.js provides, including:
- A prebuilt and polished UI for an improved user experience
- 15+ prebuilt annotation tools to enable document collaboration
- Browser-based text editing, page cropping, merging, rotating, and more
- Support for more file types with client-side PDF, MS Office, and image viewing
- Dedicated support from engineers to speed up integration
Requirements
To get started, you’ll need:
-
A package manager for installing the Nutrient library. You can use npm or Yarn. When you install Node.js,
npm
is installed by default. -
TypeScript
You can install TypeScript globally by running the following command:
npm install -g typescript
Building a TypeScript PDF viewer with PDF.js
PDF.js is a JavaScript library built by Mozilla, and it allows you to create a full-featured PDF viewer in the browser using JavaScript and the HTML5 <canvas>
element. You can integrate PDF.js with different JavaScript frameworks and libraries like React, Angular, and Vue.js.
Getting started
-
Create a new folder on your computer and change your directory to the project:
mkdir typescript-pdf-viewer cd typescript-pdf-viewer
-
Next, run
npm init --yes
to create apackage.json
file. -
Create a new
tsconfig.json
configuration file at the root of your project:
tsc --init
You can customize the rules you want the TypeScript compiler to follow. Here’s an example configuration:
{ "compilerOptions": { "target": "esnext", "module": "es6", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "removeComments": true, "preserveConstEnums": true, "sourceMap": true, "noImplicitAny": true, "strictNullChecks": true, "moduleResolution": "node" }, "include": ["src/**/*"] }
With this configuration, the compiled JavaScript code will target the latest version of ECMAScript standards for JavaScript, and it’ll use the ES6
import and export modules. The include
array tells TypeScript to compile everything in the src
folder.
Installing PDF.js and configuring webpack
-
You’ll use webpack to bundle your project. Start by installing the necessary
dev
dependencies:
npm i -D webpack webpack-cli webpack-dev-server ts-loader typescript html-webpack-plugin cross-env copy-webpack-plugin clean-webpack-plugin
Here’s what’s installed:
-
webpack
— The webpack bundler. -
webpack-cli
— Command-line interface for webpack. -
webpack-dev-server
— A local server to run webpack in the browser with live reloading. -
ts-loader
— A package that teaches webpack how to compile TypeScript. -
typescript
— The TypeScript compiler. -
clean-webpack-plugin
— A plugin that cleans the output directory before building. -
copy-webpack-plugin
— A plugin that copies files and directories to the output directory. -
html-webpack-plugin
— A plugin that generates an HTML file from a template. -
cross-env
— A package that allows you to set environment variables.
After the installation, your package.json
file will look like this:
"devDependencies": { "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^12.0.2", "cross-env": "^7.0.3", "html-webpack-plugin": "^5.6.0", "ts-loader": "^9.5.1", "typescript": "^5.6.3", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.1.0" },
-
Now, configure webpack by creating a
webpack.config.js
file at the root of your project. This will define how your project is built:
// webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const CleanPlugin = require('clean-webpack-plugin'); module.exports = { entry: path.resolve(__dirname, './src/index.ts'), output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', }, devtool: 'inline-source-map', mode: 'development', module: { rules: [ // All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`. { test: /\.tsx?$/, use: { loader: 'ts-loader' }, exclude: /node_modules/, }, { test: /\.mjs$/, include: /node_modules/, type: 'javascript/auto', }, ], }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, plugins: [ new CleanPlugin.CleanWebpackPlugin(), // Automatically insert <script src="[name].js"></script> into the page. new HtmlWebpackPlugin({ template: './src/index.html', }), // Copy the PDF file and PDF.js worker to the output path. new CopyWebpackPlugin({ patterns: [ { from: './src/example.pdf', to: './example.pdf', }, { from: './node_modules/pdfjs-dist/build/pdf.worker.mjs', to: './main.worker.js', }, ], }), ], };
This configuration specifies that your entry point is src/index.ts
, and the output will be placed in the dist
folder. The .mjs
rule is important for handling the PDF.js worker, and you’re copying both the PDF file and the worker to the output directory.
-
Install
pdfjs-dist
as a dependency:
npm install pdfjs-dist
Since pdfjs-dist
provides its own type definitions, you don’t need to install extra TypeScript typings.
Rendering a PDF
After setting up the project with webpack and TypeScript, it’s time to start using PDF.js.
-
Create the
src
directory, and inside it, create anindex.html
file:
mkdir src && touch src/index.html
-
In the
index.html
file, add a<canvas>
element where the PDF will be rendered:
<!DOCTYPE html> <html lang="en"> <head> <title>PDF.js Example</title> </head> <body> <canvas id="pdf"></canvas> </body> </html>
-
Now, create an
index.ts
file inside thesrc
directory. This file will contain the logic for rendering the PDF:
import * as pdfjsLib from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = './main.worker.js'; (async () => { const loadingTask = pdfjsLib.getDocument('example.pdf'); const pdf = await loadingTask.promise; // Load the first page. const page = await pdf.getPage(1); const scale = 1; const viewport = page.getViewport({ scale }); // Set the canvas dimensions. const canvas = document.getElementById('pdf') as HTMLCanvasElement; const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; // Render the page into the canvas. const renderContext = { canvasContext: context, viewport: viewport, }; await page.render(renderContext); console.log('Page rendered!'); })();
Running the project
-
Add some scripts to your
package.json
file to build and run the project:
"scripts": { "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", "dev": "webpack serve --config webpack.config.js", "start": "serve -l 8080 ./dist" }
-
Place the PDF file (
example.pdf
) in thesrc
directory before running the project. -
To run the project:
-
For development with live reloading, use:
npm run dev
-
To serve the built files, use:
npm start
Navigate to http://localhost:8080
to see the PDF rendered in your browser!
You can access the full code on GitHub.
Building a TypeScript PDF viewer with Nutrient
We offer a commercial TypeScript PDF library that can easily be integrated into your web application. It comes with 30+ features that let you view, annotate, edit, and sign documents directly in your browser. Out of the box, it has a polished and flexible UI that you can extend or simplify based on your unique use case.
Getting started
-
Create a new folder and change your directory to it:
mkdir typescript-pspdfkit-viewer cd typescript-pspdfkit-viewer
-
Similar to what you did above, create a
package.json
file by runningnpm init --yes
. -
Create a
tsconfig.json
file and use the following configuration:
{ "compilerOptions": { "removeComments": true, "preserveConstEnums": true, "module": "commonjs", "target": "es5", "sourceMap": true, "noImplicitAny": true, "esModuleInterop": true }, "include": ["src/**/*"] }
Installing Nutrient and configuring webpack
-
Install the Nutrient Web SDK library as a dependency with
npm
oryarn
:
npm install pspdfkit
-
Install the necessary dependencies for webpack, create a
config
directory, and place yourwebpack
configuration file inside it:
mkdir config && touch config/webpack.js
-
If you’re using webpack 4, use the example file. If you’re using the latest version of webpack — currently
^5.72.0
— use the following configuration:
// webpack.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const filesToCopy = [ // Nutrient files. { from: './node_modules/pspdfkit/dist/pspdfkit-lib', to: './pspdfkit-lib', }, // Application CSS. { from: './src/index.css', to: './index.css', }, // Example PDF. { from: './assets/example.pdf', to: './example.pdf', }, ]; /** * webpack main configuration object. */ const config = { entry: path.resolve(__dirname, '../src/index.ts'), mode: 'development', devtool: 'inline-source-map', output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ // All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`. { test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/, }, ], }, plugins: [ // Automatically insert <script src="[name].js"></script> into the page. new HtmlWebpackPlugin({ template: './src/index.html', }), // Copy the WASM/ASM and CSS files to the `output.path`. new CopyWebpackPlugin({ patterns: filesToCopy }), ], optimization: { splitChunks: { cacheGroups: { // Creates a `vendor.js` bundle that contains external libraries (including `pspdfkit.js`). vendor: { test: /node_modules/, chunks: 'initial', name: 'vendor', priority: 10, enforce: true, }, }, }, }, }; module.exports = config;
Displaying the PDF
-
Add the PDF document you want to display to the
assets
directory. You can use our demo document as an example. -
Create an
index.html
file inside thesrc
directory and add the following code:
<!DOCTYPE html> <html> <head> <title>PSPDFKit for Web — TypeScript example</title> <link rel="stylesheet" href="index.css" /> </head> <body> <div class="container"></div> </body> </html>
This adds an empty <div>
element to where Nutrient will be mounted.
-
Declare the height of this element in your CSS file like this:
.container {
height: 100vh;
}
-
Now, create an
index.ts
file inside thesrc
directory:
import PSPDFKit from 'pspdfkit'; function load(document: string) { console.log(`Loading ${document}...`); PSPDFKit.load({ document, container: '.container', }) .then((instance) => { console.log('PSPDFKit loaded', instance); }) .catch(console.error); } load('example.pdf');
Here, you’ve imported the PSPDFKit
library and created a function that loads the PDF document.
Running the project
-
Now, write some scripts in the
package.json
file to start your server:
"scripts": { "build": "cross-env NODE_ENV=production webpack --config config/webpack.js", "prestart": "npm run build", "dev": "tsc", "start": "serve -l 8080 ./dist" },
-
Run
npm start
to start the server. Navigate tohttp://localhost:8080
to see the contents of thedist
directory.
Opening different PDF files
The current viewer is loading the PDF file you have in the assets/example.pdf
file. But you can also open different PDF files by adding an input field with the file
type on the index.html
file:
<div> <input type="file" class="chooseFile" accept="application/pdf" /> </div> <br /> <div class="container"></div>
Now, go to the index.ts
file and add the following code:
interface HTMLInputEvent extends Event { target: HTMLInputElement & EventTarget; } let objectUrl = ''; document.addEventListener('change', function (event: HTMLInputEvent) { if ( event.target && event.target.className === 'chooseFile' && event.target.files instanceof FileList ) { PSPDFKit.unload('.container'); if (objectUrl) { URL.revokeObjectURL(objectUrl); } objectUrl = URL.createObjectURL(event.target.files[0]); load(objectUrl); } });
Here, you’ve added a change
event listener to the <input>
element. When the user selects a file, you’ll unload the current PDF and load the new one.
If you want to learn how to add annotations to your application, check out the Open and annotate PDFs in a TypeScript app blog post. You can see the example project with annotation support on GitHub.
Adding even more capabilities
Once you’ve deployed your viewer, you can start customizing it to meet your specific requirements or easily add more capabilities. To help you get started, here are some of our most popular TypeScript guides:
- Adding annotations
- Editing documents
- Filling PDF forms
- Adding signatures to documents
- Real-time collaboration
- Redaction
- UI customization
Conclusion
In this tutorial, you saw how to build a TypeScript PDF viewer — first with PDF.js, and then with the Nutrient TypeScript PDF viewer library that allows you to display and render PDF files in your TypeScript application.
We created similar how-to blog posts using different web frameworks and libraries:
- How to build an Angular PDF viewer with PDF.js
- How to build a Vue.js PDF viewer with PDF.js
- How to build a React PDF viewer with PDF.js
- How to build a Bootstrap 5 PDF viewer with PDF.js
- How to build an Electron PDF viewer with PDF.js
- How to build a jQuery PDF viewer with PDF.js
- How to build a JavaScript PDF viewer with PDF.js
To get started with our TypeScript PDF viewer, try it for free, or launch our web demo.
FAQ
Here are a few frequently asked questions about building a TypeScript PDF viewer.
What are the main steps to build a TypeScript PDF viewer with PDF.js?
-
Set up a project with Node.js, TypeScript, and webpack.
-
Install and configure PDF.js.
-
Render a PDF file in a
<canvas>
element using TypeScript.
What additional features does Nutrient Web SDK offer compared to PDF.js?
Nutrient Web SDK offers a prebuilt UI, annotation tools, document editing capabilities, support for various file types, and dedicated engineering support.
How can I get started with Nutrient Web SDK?
-
Create a new project folder and set up the project with Node.js and TypeScript.
-
Install Nutrient and configure webpack.
-
Integrate Nutrient into your application to display and interact with PDF documents.
Where can I find the full code for the examples in this tutorial?
You can access the full code for the PDF.js example on GitHub and the Nutrient example on GitHub.