Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Short answer: the correct method depends on where your JavaScript runs. In a browser, doc.save("file.pdf") starts a download; the page cannot silently force an arbitrary local folder. To let a person choose a destination, use the browser’s supported showSaveFilePicker() API and provide a normal-download fallback. In Node.js, write the generated PDF bytes to an explicit filesystem path with Node’s fs API. jsPDF documents doc.save() in Node as writing to the current working directory, not necessarily the folder containing your script.

Choose the runtime before choosing a folder

A browser tab and a Node.js process have different file permissions. The following table is the practical decision point:

Where the code runs How the destination is chosen Best approach Important limitation
Browser The browser download settings or the user through a file picker doc.save() for a simple download; showSaveFilePicker() for an interactive location choice A web page cannot silently select any arbitrary path such as C:Reportsfile.pdf.
Node.js Your application supplies a path Generate PDF data and call fs.writeFile() (or its promise form) The process needs a valid path and operating-system write permission.

Browser JavaScript: the normal jsPDF download

For a browser page, jsPDF’s documented save call is the simplest route:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { jsPDF } = window.jspdf;

const doc = new jsPDF();
doc.text("Hello world!", 10, 10);
doc.save("example.pdf");

The filename is a name, not a promise that the page can choose a full folder path. The browser decides where ordinary downloads go. Depending on the user’s settings, it may use a default Downloads directory, ask for a location each time, or apply enterprise policy. A string such as "C:/Reports/example.pdf" should not be treated as a reliable way to override those rules.

Use a user-selected destination with the File System Access API

Where the target browser supports the File System Access API, window.showSaveFilePicker() opens a save dialog. The call must normally happen as a result of a user action, such as a button click. The user, not the page, confirms the destination.

const { jsPDF } = window.jspdf;

async function makePdf() {
  const doc = new jsPDF();
  doc.text("Hello world!", 10, 10);

  // jsPDF versions expose PDF bytes through output(). Confirm the
  // exact output mode supported by the version installed in your app.
  const pdfBlob = doc.output("blob");

  if ("showSaveFilePicker" in window) {
    const handle = await window.showSaveFilePicker({
      suggestedName: "example.pdf",
      types: [{
        description: "PDF document",
        accept: { "application/pdf": [".pdf"] }
      }]
    });
    const writable = await handle.createWritable();
    await writable.write(pdfBlob);
    await writable.close();
    return;
  }

  // Fallback for browsers without the picker API.
  doc.save("example.pdf");
}

document.querySelector("#save-pdf").addEventListener("click", () => {
  makePdf().catch((error) => {
    if (error.name !== "AbortError") console.error(error);
  });
});

Provide a button with the save-pdf ID and load jsPDF before this script. If the user cancels the dialog, browsers generally reject the promise with an AbortError; the example leaves that cancellation quiet while logging other failures. The picker API’s availability and behavior vary by browser and version, so test every browser you intend to support. Keep the ordinary doc.save() fallback rather than assuming the picker exists everywhere.

When a directory picker is useful

window.showDirectoryPicker() lets a user select a directory, after which your page can create a file in that directory through the returned handle. It is still an interactive permission flow, not a way to discover or write to an arbitrary hidden local path. For a single PDF, showSaveFilePicker() is usually clearer because it asks for both the filename and destination in one dialog.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Node.js: write to an explicit path

jsPDF’s Node example uses doc.save("a4.pdf") and documents that this writes to the current working directory. The current working directory is the directory from which the process was launched; it can differ from the directory containing your JavaScript file. If the output location matters, construct it explicitly and write the PDF data with Node’s filesystem API.

CommonJS example with an absolute output path

const { jsPDF } = require("jspdf");
const path = require("node:path");
const fs = require("node:fs/promises");

async function createPdf() {
  const doc = new jsPDF();
  doc.text("Hello world!", 10, 10);

  // Confirm the output mode against the jsPDF version in your project.
  const arrayBuffer = doc.output("arraybuffer");
  const pdfBuffer = Buffer.from(arrayBuffer);

  const outputDir = path.resolve(process.cwd(), "reports");
  const outputFile = path.join(outputDir, "example.pdf");

  await fs.mkdir(outputDir, { recursive: true });
  await fs.writeFile(outputFile, pdfBuffer);
  console.log(`Saved PDF to ${outputFile}`);
}

createPdf().catch((error) => {
  console.error("Could not save PDF:", error);
  process.exitCode = 1;
});

doc.output("arraybuffer") is converted to a Node Buffer, then passed to fs.writeFile(). Check the output method supported by the exact jsPDF release installed in your project; keep the conversion appropriate to that release. The mkdir call handles a missing reports directory. Remove recursive: true only if you deliberately require the directory to exist already.

Save beside the module instead of beside the launch directory

If you want a path relative to the source file, resolve it from the module location rather than process.cwd(). In CommonJS:

const path = require("node:path");
const outputFile = path.join(__dirname, "reports", "example.pdf");

In an ES module, derive the directory from import.meta.url:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import path from "node:path";
import { fileURLToPath } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const outputFile = path.join(here, "reports", "example.pdf");

Use path.join() or path.resolve() instead of concatenating slashes yourself. On Windows, a path such as C:Reportsexample.pdf is valid only when the process has access to that drive and directory; on Linux and macOS, use the corresponding mounted path.

Write a PDF returned by an asynchronous job

If PDF creation is inside an async function, await both directory creation and the write. This prevents a race in which the write starts before the destination directory exists:

await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(outputFile, Buffer.from(doc.output("arraybuffer")));

Do not confuse jsPDF’s notes about restricted filesystem reads in Node runtimes with writing generated output. Reading an input file and writing a newly generated PDF are separate operations, and both remain subject to the runtime and operating-system permissions in your deployment.

Browser download, picker, or Node path?

Requirement Use Why
Just give the visitor a PDF doc.save("name.pdf") Smallest implementation; browser download behavior applies.
Let the visitor choose a folder and filename showSaveFilePicker() The browser presents a controlled save dialog and returns a file handle.
Save automatically on a server or worker Node.js plus fs.writeFile() Your code can choose a path, create directories, and handle failures.
Run in browsers without the picker Picker feature detection plus doc.save() fallback Preserves a working download path when the newer API is unavailable.

Troubleshooting specific-folder saves

The file still goes to Downloads

You are using the normal browser download flow. doc.save() does not grant a page arbitrary folder access. Ask the user to change browser download settings, or use the interactive picker when the target browser supports it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

showSaveFilePicker is not a function

The API is unavailable in that browser, context, or version. Keep the feature test, test on your supported browser matrix, and fall back to doc.save(). Do not remove the fallback based on a test in only one browser.

The picker opens and then the operation fails

Check that the call is made from a user-initiated event, that the user did not cancel it, and that the writable stream is closed. Log errors other than AbortError; a denied permission or an unavailable destination should be visible to the user rather than silently ignored.

ENOENT from Node’s writeFile

The parent directory does not exist, or a path component is misspelled. Create it with fs.mkdir(dir, { recursive: true }) and print the resolved path before writing.

EACCES or a permission error

The operating-system account running Node cannot write there, the file is locked, or a security policy blocks the location. Choose a writable application directory, correct its permissions, and avoid assuming that a developer’s home directory is writable in production.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The file is empty or corrupt

Make sure you write the PDF bytes, not a JavaScript object or a data URL string with an unintended encoding. Use the output mode required by your jsPDF version, convert an ArrayBuffer to Buffer in Node, and await the write before the process exits.

The output appears in an unexpected directory

Log process.cwd() and the final value of outputFile. Relative paths are resolved against the process current working directory, which depends on how the script, service, container, or task runner was started.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the real goal is to turn a web page into an image or PDF rather than generate a PDF with jsPDF, ScreenshotNeo provides a one-request screenshot API. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

JavaScript, Python, and other clients can use the same endpoint. The API returns PNG, JPEG, WebP, or PDF according to the request options; see the ScreenshotNeo documentation for the current parameters.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 screenshots per month with no card required. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it without adding a card.

Practical checklist

  • Identify whether the code runs in a browser or Node.js.
  • For a browser download, use doc.save() and let browser settings control the folder.
  • For a user-selected browser destination, feature-detect showSaveFilePicker() and retain a fallback.
  • For Node.js, resolve an explicit path, create its parent directory, convert PDF bytes correctly, and await fs.writeFile().
  • Log the resolved destination and handle cancellation, missing directories, and permissions.

Frequently Asked Questions

Can I pass a Windows folder path to jsPDF’s browser save method?

No. In browser JavaScript, a path string is not a reliable permission mechanism. Use the browser’s download flow or an interactive save picker.

What is Node.js’s default jsPDF save location?

The jsPDF Node example documents doc.save() as saving in the process’s current working directory.

Does showSaveFilePicker() work in every browser?

Its availability differs by browser and version. Feature-detect it and keep a normal-download fallback.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why should I create the output directory first in Node.js?

fs.writeFile() writes a file but does not create missing parent directories; create them with fs.mkdir() before writing.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.