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
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.
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.
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.
🌐
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 ·
🌐
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
🌐
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
🌐
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.
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

🌐
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)
2 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.
🌐
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.
🌐
Hacker News
news.ycombinator.com › item
Show HN: Pdf.js Express – PDF annotation, e-signatures, and form filling | Hacker News
January 15, 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 ...
🌐
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
🌐
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.
🌐
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.
🌐
GitHub
github.com › pdfjs-express › pdfjs-express-vue-sample
PDF.js Express - Vue sample
PDF.js Express is a powerful JavaScript-based PDF Library that leverages PDF.js and adds additional features such as annotations, form support, and digitial signatures.
Starred by 7 users
Forked by 6 users
Languages   Vue 52.8% | HTML 26.4% | JavaScript 20.8%
🌐
GitHub
github.com › pdfjs-express › pdfjs-express-react-sample
GitHub - pdfjs-express/pdfjs-express-react-sample
PDF.js Express is a powerful JavaScript-based PDF Library that leverages PDF.js and adds additional features such as annotations, form support, and digitial signatures.
Starred by 5 users
Forked by 12 users
Languages   JavaScript 48.8% | HTML 39.0% | CSS 12.2%