How to Create a PDF from HTML with PDFShift in Node.js
Create a PDF from raw HTML or a URL with PDFShift in Node.js. See runnable code, options, troubleshooting, and when a screenshot API is a better fit.
To create a PDF from HTML with PDFShift in Node.js, send a POST request to https://api.pdfshift.io/v3/convert/pdf, put your HTML in the JSON source property, authenticate with the X-API-Key header, and save the response bytes as a .pdf file. You can also put a fetchable page URL in source. PDFShift recommends raw HTML when you already have the markup or need to convert a private document.
1. Choose raw HTML or a URL
Use raw HTML when your Node application creates the markup, the document is private, or you want to control the HTML and its rendering inputs. The API receives the markup directly instead of fetching the source page. Inline styles and scripts where practical if you want to reduce external requests. PDFShift recommends raw HTML for this reason, but its guide does not publish a measured speed comparison.
Use a URL when the page is publicly reachable by PDFShift and you want the service to fetch it. The URL goes in the same source property. A URL that only works inside your network or requires an authenticated browser session may not be accessible to the converter.
| Input | Choose it when | What to account for |
|---|---|---|
| Raw HTML | Your app owns the HTML, or the document is private or generated dynamically. | Include or make reachable the CSS, fonts, images, and scripts the document needs. Inline assets when appropriate. |
| URL | The page is reachable by the conversion service and you want it fetched as a page. | Remote assets and page access affect rendering. Do not assume a private local URL is reachable from the service. |
2. Create a PDF from raw HTML in Node.js
This example uses SuperAgent, the client shown in PDFShift’s raw-HTML Node guide. Install it with npm install superagent. Set the API key in your environment before running the script:
export PDFSHIFT_API_KEY="your_api_key"
node create-pdf.js
Save the following as create-pdf.js:
const superagent = require('superagent');
const fs = require('node:fs/promises');
async function main() {
const apiKey = process.env.PDFSHIFT_API_KEY;
if (!apiKey) {
throw new Error('Set the PDFSHIFT_API_KEY environment variable');
}
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Example PDF</title>
<style>
body { font: 16px sans-serif; margin: 40px; }
h1 { color: #183153; }
</style>
</head>
<body>
<h1>PDFShift from Node.js</h1>
<p>This PDF was generated from HTML.</p>
</body>
</html>`;
const response = await superagent
.post('https://api.pdfshift.io/v3/convert/pdf')
.set('X-API-Key', apiKey)
.responseType('buffer')
.send({ source: html });
await fs.writeFile('result.pdf', response.body);
console.log('Wrote result.pdf');
}
main().catch((error) => {
console.error('PDF conversion failed:', error.message);
process.exitCode = 1;
});
The API response is binary PDF data. The explicit buffer response type ensures the client treats it as bytes, and the asynchronous filesystem API avoids blocking the Node event loop while writing the output. Keep the key in an environment variable or secret store; do not commit it to source control.
3. Convert a URL instead
For a fetchable page, keep the same endpoint and authentication but set source to the page URL. Here is a runnable Axios example:
npm install axios
const axios = require('axios');
const fs = require('node:fs/promises');
async function main() {
const apiKey = process.env.PDFSHIFT_API_KEY;
if (!apiKey) throw new Error('Set PDFSHIFT_API_KEY');
const response = await axios.post(
'https://api.pdfshift.io/v3/convert/pdf',
{ source: 'https://example.com' },
{
headers: { 'X-API-Key': apiKey },
responseType: 'arraybuffer',
timeout: 60000
}
);
await fs.writeFile('result.pdf', Buffer.from(response.data));
console.log('Wrote result.pdf');
}
main().catch((error) => {
console.error('PDF conversion failed:', error.message);
process.exitCode = 1;
});
Replace https://example.com with the target page. Choose a URL only if it is accessible to PDFShift and its required assets can load from the conversion environment.
4. cURL and Python equivalents
These are useful for isolating whether a problem is in the Node client or in the API request. Set the same API key as an environment variable first.
cURL
curl --fail-with-body \
-X POST 'https://api.pdfshift.io/v3/convert/pdf' \
-H "X-API-Key: $PDFSHIFT_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"source":"<!doctype html><html><body><h1>Hello PDF</h1></body></html>"}' \
-o result.pdf
Python
import os
import requests
api_key = os.environ["PDFSHIFT_API_KEY"]
html = "<!doctype html><html><body><h1>Hello PDF</h1></body></html>"
response = requests.post(
"https://api.pdfshift.io/v3/convert/pdf",
headers={"X-API-Key": api_key},
json={"source": html},
timeout=60,
)
response.raise_for_status()
with open("result.pdf", "wb") as output:
output.write(response.content)
5. Options and rendering inputs
The core request fields established by the cited PDFShift Node guides are the endpoint, API key header, and source input. The official Node guide index also documents separate tutorials for additional conversion scenarios. Use the matching current API documentation for the exact option names and accepted values before adding them; the guide index alone does not establish a complete parameter schema.
- HTML, CSS, and JavaScript: The guide index covers CSS and JavaScript inputs. Inline what is practical when converting raw HTML, particularly when reducing remote asset requests matters.
- Headers, cookies, and secured pages: Dedicated tutorials cover secured pages, custom headers, and cookies. Use these when the page requires access credentials, and avoid placing secrets in public source or logs.
- Waiting for content: A tutorial covers waiting for a custom element. This is relevant when charts or other page content is added after initial load.
- Page layout: Tutorials cover headers and footers, selected pages, full-height documents, and watermarks. Check the corresponding guide for supported configuration.
- Timeouts and delivery: Tutorials cover timeout, webhooks, remote storage, and Amazon S3 delivery. These options can fit long-running or asynchronous workflows.
PDFShift publishes Node examples for Axios, Bent, Got, Needle, NodeFetch, SuperAgent, and Unfetch. Use the client already present in your project unless you have a specific reason to add another dependency.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Unauthorized response | The X-API-Key header is missing, misspelled, or contains the wrong key. |
Check that PDFSHIFT_API_KEY is set in the process environment and that the request sends it as X-API-Key. |
| Output file is not a readable PDF | The HTTP client decoded binary response data as text, or an error response was saved as if it were a PDF. | Set a binary response type such as SuperAgent’s buffer or Axios’s arraybuffer. Check the HTTP status before writing the body. |
| Images or styles are missing | Remote assets did not load, paths are invalid from the converter’s environment, or the document references assets unavailable to it. | Check asset URLs and access requirements. For raw HTML, include the necessary styles and use inline CSS or otherwise reachable assets where practical. PDFShift’s Help Center has a specific article on missing images. |
| Chart or dynamic content is absent | The page had not finished adding content when conversion began. | Use the documented wait-for-element approach for the relevant workflow and confirm the element exists in the rendered page. |
| Content overlaps a header or footer | The printable content area and repeated header/footer layout conflict. | Review the header/footer setup and page spacing. PDFShift’s Help Center covers content spilling beneath headers or footers. |
| Conversion times out | The page or its assets take too long, or the selected plan’s timeout is reached. | Reduce unnecessary remote requests, prefer raw HTML when suitable, and consult the current plan limits and timeout guidance. Do not assume a client timeout increase extends the service-side limit. |
| Local or private URL cannot be fetched | The conversion service cannot reach a URL available only on your machine or internal network. | Send raw HTML when your app owns the markup, or provide a source reachable by the service using an appropriate secured-page workflow. |
7. Reliability, performance, and cost
For reliability, validate the API key before making a request, set a client timeout appropriate to your application, check the response status, and write the returned bytes to a deliberate output path. Treat transient network failures separately from invalid credentials or invalid input. If conversion is part of a user-facing request, consider whether a queued or webhook-based workflow fits the latency requirements; PDFShift lists tutorials for asynchronous responses and webhooks.
For performance, raw HTML can avoid fetching the source page itself. PDFShift recommends it and notes that inline styles and scripts can reduce external requests and asset loading. This is a vendor recommendation, not a quantified benchmark. External images, fonts, stylesheets, and scripts can still add requests when referenced by the HTML.
PDFShift’s pricing page, accessed October 3, 2026, listed a free plan with 50 credits per month, a 15 MB maximum file size, and a 30-second timeout. It stated that one credit is counted per 5 MB of generated data. These are time-sensitive plan details; verify the current pricing page before relying on them. The pricing page also lists features such as CSS/JavaScript injection and advanced headers/footers among basic features, and identifies no file size limit, S3 delivery, and parallel/asynchronous responses among listed features.
8. When a PDF is the wrong output
PDFShift creates a paginated document. If your goal is to capture a web page as an image for a preview, audit, or visual record, a screenshot API may be a better fit. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It returns PNG, JPEG, or WebP screenshots, or PDFs, from one GET request. Its clean capture flow accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Only clean shots are billed: bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. ScreenshotNeo also offers an MCP server for AI agents, with take_screenshot, get_page_info, and capture_pdf tools.
Or skip the browser setup
For a page screenshot or PDF capture, make one request to the ScreenshotNeo API:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is available on every plan. Sign up free for 1,000 screenshots a month, with no card required.
9. FAQ
Does PDFShift accept a complete HTML document?
Yes. The raw-HTML guide sends a document string in the JSON source property. Include a doctype and document structure for predictable markup.
Can I use PDFShift with the Node HTTP client I already have?
PDFShift publishes examples for several clients, including Axios, Got, NodeFetch, and SuperAgent. The request still needs the API endpoint, X-API-Key, a source value, and binary response handling.
Can PDFShift render a page that requires authentication?
The guide index includes secured-page, header, and cookie tutorials. Follow the relevant current PDFShift instructions for the access method the page requires.
Is PDFShift’s free allowance a fixed number of documents?
The pricing page accessed for this article described 50 monthly credits and one credit per 5 MB of generated data. Check the current pricing page because plan limits can change.


