Recommended Free Tools
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:
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallconst { 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.
#1 Best Overall
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.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →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.
Rank #2
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:
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.
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.
Rank #4
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.
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.
Best Value
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.
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.
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.
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.
Quick Recap
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.

