Specifying how a file download is handled all comes down to the Content-disposition header. You can also specify the name of the file here as well. We also set the Content-type to ensure the browser knows what to do with the file given to it.

Express.js Example:

app.post('/url/to/hit', function(req, res, next) {
  var stream = fs.createReadStream('/location/of/pdf');
  var filename = "WhateverFilenameYouWant.pdf"; 
  // Be careful of special characters

  filename = encodeURIComponent(filename);
  // Ideally this should strip them

  res.setHeader('Content-disposition', 'inline; filename="' + filename + '"');
  res.setHeader('Content-type', 'application/pdf');

  stream.pipe(res);
});

Now if you look more closely at the Content-disposition, you'll notice the inline; field is what sets how the browser reacts to the file. If you want to force downloads, you can do so by setting inline; to attachment;

I've also found out (by being burnt a couple times), that if you set special characters in your filename, it can break. So I encodeURIComponent() the filename to ensure that doesn't happen.

Hope that helps others trying to figure out the same!

Edit

In the time between me posting this originally and now, I've found out how to correctly encode the content-disposition's filename parameter. According to the spec, the filename should be RFC5987 encoded. I ended up finding an example code snippet from MDN that correctly handles the encoding here (encodeURIComponent() isn't the entirely correct format for this field).

MDN Snippet

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

console.log(header); 
// logs "Content-Disposition: attachment; filename*=UTF-8''my%20file%282%29.txt"

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}

Another note on top of this one, browsers don't fully comply with the spec either. Some characters will still come back incorrectly from a download (at least when I tested it).

You can get around this problem by updating how your downloads work. If your download URL ends with the filename (and you don't supply a filename attribute in the header), it will correctly get the filename from the URL encoded value. IE 'http://subdomain.domain-url.com/some/path/to/downloads/' + encodeURIComponent("You're there, download this!.pdf")

Jeeze, and all to supply a file name to your downloads!

Answer from AlbertEngelB on Stack Overflow
🌐
PDF.js Express
pdfjs.express
PDF.js Viewer: Annotate, Form Fill | Easy Setup | PDF.js Express
Fast & easy PDF.js Viewer integration with PDF.js Express. Add annotations, fill forms & sign PDFs in a web browser. Fully supported - download & try it free
PDF.js Viewer Demo
Try the PDF.js Express Viewer demo: 26 out-of-the-box annotations, PDF form filling, e-signatures, UI customization, light/dark themes & more.
About Us | Apryse
PDF.js Express is a commercial PDF.js Viewer that adds a flexible & modern UI to PDF viewers that include features like annotations, form filling & e-signatures
PDF.js Express Documentation
PDF.js Express Plus is a commercial PDF web viewer that wraps around the PDF.js open-source rendering engine. It offers developers a way to quickly add annotation, e-signatures, and form filling to their PDF viewer.
PDF.js Express Viewer Download & Integration
For resources on how to use more of our PDF.js Express API, check out the PDF.js Express guides and API. To integrate PDFJS Express as a module, view our npm guide.
🌐
npm
npmjs.com › package › @pdftron › pdfjs-express-viewer
@pdftron/pdfjs-express-viewer - npm
[PDFJS Express](https://pdfjs.express) is a powerful JavaScript-based PDF Library that wraps [PDF.js](https://mozilla.github.io/pdf.js/). It provides a slick out-of-the-box responsive UI that interacts with the core library to view, annotate ...
      » npm install @pdftron/pdfjs-express-viewer
    
Published   Jul 04, 2024
Version   8.7.5
Author   PDFTron
🌐
GitHub
github.com › pdfjs-express
PDF.js Express · GitHub
Building a PDF.js viewer from the ground up · TypeScript 10 4 · pdfjs-express-vue-sample · pdfjs-express-vue-sample Public · Vue 7 6 · bootstrap-pdfjs-viewer · bootstrap-pdfjs-viewer Public · A simple Bootstrap website with a PDF.js viewer · JavaScript 6 4 ·
🌐
PDF Association
pdfa.org › building-pdf-js-express
Building PDF.js Express – PDF Association
Announcing PDF.js Express, a commercial viewer that wraps a modern React UI around the PDF.js rendering engine to enable PDF annotations, form filling, and signing inside a web app.
🌐
npm
npmjs.com › package › @pdftron › pdfjs-express
@pdftron/pdfjs-express - npm
[PDFJS Express](https://pdfjs.express) is a powerful JavaScript-based PDF Library that wraps [PDF.js](https://mozilla.github.io/pdf.js/). It provides a slick out-of-the-box responsive UI that interacts with the core library to view, annotate ...
      » npm install @pdftron/pdfjs-express
    
Published   Jul 04, 2024
Version   8.7.5
Author   PDFTron
Top answer
1 of 7
43

Specifying how a file download is handled all comes down to the Content-disposition header. You can also specify the name of the file here as well. We also set the Content-type to ensure the browser knows what to do with the file given to it.

Express.js Example:

app.post('/url/to/hit', function(req, res, next) {
  var stream = fs.createReadStream('/location/of/pdf');
  var filename = "WhateverFilenameYouWant.pdf"; 
  // Be careful of special characters

  filename = encodeURIComponent(filename);
  // Ideally this should strip them

  res.setHeader('Content-disposition', 'inline; filename="' + filename + '"');
  res.setHeader('Content-type', 'application/pdf');

  stream.pipe(res);
});

Now if you look more closely at the Content-disposition, you'll notice the inline; field is what sets how the browser reacts to the file. If you want to force downloads, you can do so by setting inline; to attachment;

I've also found out (by being burnt a couple times), that if you set special characters in your filename, it can break. So I encodeURIComponent() the filename to ensure that doesn't happen.

Hope that helps others trying to figure out the same!

Edit

In the time between me posting this originally and now, I've found out how to correctly encode the content-disposition's filename parameter. According to the spec, the filename should be RFC5987 encoded. I ended up finding an example code snippet from MDN that correctly handles the encoding here (encodeURIComponent() isn't the entirely correct format for this field).

MDN Snippet

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

console.log(header); 
// logs "Content-Disposition: attachment; filename*=UTF-8''my%20file%282%29.txt"

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}

Another note on top of this one, browsers don't fully comply with the spec either. Some characters will still come back incorrectly from a download (at least when I tested it).

You can get around this problem by updating how your downloads work. If your download URL ends with the filename (and you don't supply a filename attribute in the header), it will correctly get the filename from the URL encoded value. IE 'http://subdomain.domain-url.com/some/path/to/downloads/' + encodeURIComponent("You're there, download this!.pdf")

Jeeze, and all to supply a file name to your downloads!

2 of 7
14

My Solution for sending a PDF directly to the Browser:

app.get('/my/pdf', function (req, res) {
    var doc = new Pdf();
    doc.text("Hello World", 50, 50);

    doc.output( function(pdf) {
        res.type('application/pdf');
        res.end(pdf, 'binary');
    });
});

res.end() with the second param 'binary' did the trick in my case. Otherwise express interpret it as a string

🌐
CodeSandbox
codesandbox.io › examples › package › @pdftron › pdfjs-express
@pdftron/pdfjs-express examples - CodeSandbox
Use this online @pdftron/pdfjs-express playground to view and fork @pdftron/pdfjs-express example apps and templates on CodeSandbox.
🌐
GitHub
github.com › pdfjs-express › pdfjs-express-typescript-sample
GitHub - pdfjs-express/pdfjs-express-typescript-sample
It provides a slick out-of-the-box responsive UI that interacts with the core library to view, annotate and manipulate PDFs that can be embedded into any web project. This repo is specifically designed for any users interested in integrating WebViewer into a TypeScript project. Express has a comprehensive definition file ready for use by just adding a reference to the definition file. Before you begin, make sure your development environment includes Node.js.
Author   pdfjs-express
Find elsewhere
🌐
SaaSworthy
saasworthy.com › home › new saas software › pdf.js express
PDF.js Express - Features & Pricing (December 2025)
3 days ago - PDF.js Express is an open source commercial PDF viewer for developers, allowing them to add e-signatures, annotations and forms within their PDF viewer. The software is equipped with three essential components, UI, webviewer.js and the core.
🌐
Apryse
apryse.com › blog › how-to-use-pdf-js
PDF.js Viewer | Apryse
May 8, 2020 - As you can see, building a basic PDF viewer with PDF.js is pretty straightforward. If you require additional capabilities, like PDF annotation, filling forms, or e-signatures, consider PDF.js Express, which provides a PDF.js-based viewer with out-of-the-box annotation, PDF form fill, and signing.
🌐
Hacker News
news.ycombinator.com › item
Show HN: Pdf.js Express – PDF annotation, e-signatures, and form filling | Hacker News
January 14, 2020 - We're super excited to officially launch PDF.js Express [1] · PDF.js Express wraps a modern React UI around the PDF.js rendering engine to enable PDF annotation, form filling, and signing inside your web app. We've also made some improvements to PDF.js text search, and taken a different ...
🌐
Nutrient
nutrient.io › blog › sdk › how to build an expressjs pdf viewer
How to build an Express.js PDF viewer
March 20, 2023 - The guide provides step-by-step instructions for setting up an Express server, creating routes to serve PDF files, integrating Mozilla’s PDF.js library for client-side rendering, and implementing proxy routes for proper file serving in a Node.js web application.
🌐
PDF.js Express
pdfjs.community
PDF.js Express - A support community for PDF.js Express
A support community focused on PDF.js, an open sourced browser based PDF library
🌐
OutSystems
outsystems.com › forge › component-overview › 15054 › pdf-js-express-viewer-o11
PDF JS Express Viewer - Overview (O11) | OutSystems
OutSystems Implementation for https://pdfjs.express Add a PDF.js viewer to any application with out-of-the-box annotations, PDF form filling, and signing.
🌐
PDF.js Express
pdfjs.express › documentation › samples
PDF.js Express Web Viewer Samples | Documentation
Customize UI Shows how to customize the UI using PDF.js Express Web Viewer APIs. Includes modifying header, enabling/disabling features and hiding/showing individual elements.