How to Generate a PDF from HTML with DocRaptor and Node.js
Generate PDFs from HTML in Node.js with DocRaptor. Handle binary responses, assets, test mode, JavaScript, errors, and longer jobs safely.
To generate a PDF from HTML with DocRaptor in Node.js, send a server-side POST request to https://api.docraptor.com/docs with your HTML (or a document URL), PDF settings, and API key. Treat a successful response as binary bytes: save those bytes or return them with a PDF content type. Do not decode a successful response as text.
This guide follows DocRaptor’s documented HTTP API and Node.js examples. Check the current API reference before production use, since field shapes and product behavior can change.
1. Choose HTML content or a document URL
Use document_content when your application generates or already has the HTML string. This gives your server direct control over the document submitted. Use document_url when the HTML is already hosted and DocRaptor can retrieve it. With either method, the renderer must be able to fetch referenced stylesheets, fonts, images, and other assets.
| Input | Use it when | Asset handling |
|---|---|---|
document_content |
You generate HTML in your Node app or need to submit a specific string. | Use absolute asset URLs or set a base URL for relative references. |
document_url |
The source document is already available at a URL accessible to DocRaptor. | Ensure the page and its assets are reachable by the renderer. |
The DocRaptor Node.js guide shows prince_options.baseurl for resolving relative assets in supplied HTML. An absolute asset URL is another option. A page that works in your browser may still fail to render if it depends on a private network, a logged-in session, or browser-only state unavailable to the renderer.
2. Create a Node.js project and configure credentials
Use Node.js 18 or newer for the built-in fetch used below. Store the API key in a server-side environment variable. Never place it in browser JavaScript, a public web bundle, or client-visible HTML.
mkdir docraptor-pdf
cd docraptor-pdf
npm init -y
Set DOCRAPTOR_API_KEY in your deployment’s secret configuration. For a local shell session, you can export it before starting the script:
export DOCRAPTOR_API_KEY="your_api_key"
3. Generate and save a PDF with Node.js
This complete example submits HTML, enables DocRaptor test mode, requests PDF output, checks the HTTP status, and writes the binary response to output.pdf. Test mode creates a watermarked PDF, so use it to validate your integration rather than as production output.
import { writeFile } from 'node:fs/promises';
const apiKey = process.env.DOCRAPTOR_API_KEY;
if (!apiKey) {
throw new Error('Set DOCRAPTOR_API_KEY in the server environment.');
}
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Invoice</title>
<style>
body { font: 14px Arial, sans-serif; margin: 36px; }
h1 { color: #18324b; }
</style>
</head>
<body>
<h1>Invoice</h1>
<p>Generated from HTML with DocRaptor.</p>
</body>
</html>`;
const requestBody = {
user_credentials: { username: apiKey },
doc: {
document_content: html,
name: 'invoice.pdf',
type: 'pdf',
test: true
}
};
const response = await fetch('https://api.docraptor.com/docs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`DocRaptor returned HTTP ${response.status}: ${errorBody}`);
}
const pdfBytes = new Uint8Array(await response.arrayBuffer());
if (pdfBytes.byteLength === 0) {
throw new Error('DocRaptor returned an empty response.');
}
await writeFile('output.pdf', pdfBytes);
console.log(`Saved output.pdf (${pdfBytes.byteLength} bytes)`);
The example uses the documented request structure with credentials and a doc object. DocRaptor pages show slightly different request shapes and type field conventions; consult the live reference when adapting fields. The essential handling rule remains: a successful direct PDF response is binary.
4. Use Axios if it is already in your application
DocRaptor’s Node.js tutorial demonstrates Axios. Set responseType: 'arraybuffer' so the PDF body is not decoded as a string. Install Axios with npm install axios, then use this server-side pattern:
import axios from 'axios';
import { writeFile } from 'node:fs/promises';
const apiKey = process.env.DOCRAPTOR_API_KEY;
if (!apiKey) throw new Error('Missing DOCRAPTOR_API_KEY');
try {
const response = await axios.post(
'https://api.docraptor.com/docs',
{
user_credentials: { username: apiKey },
doc: {
document_content: '<html><body><h1>Report</h1></body></html>',
name: 'report.pdf',
type: 'pdf',
test: true
}
},
{ responseType: 'arraybuffer' }
);
await writeFile('report.pdf', Buffer.from(response.data));
} catch (error) {
if (error.response) {
const details = Buffer.from(error.response.data).toString('utf8');
throw new Error(`DocRaptor returned HTTP ${error.response.status}: ${details}`);
}
throw error;
}
Only decode the body on the error path. A non-success response may contain an XML error response, not a PDF. Do not write that error body to a file named .pdf.
5. Return the PDF from an application endpoint
For an Express route, keep the DocRaptor call on the server and send the PDF bytes to the caller with PDF headers. Install Express if needed with npm install express. This example assumes the request construction shown above:
import express from 'express';
const app = express();
app.use(express.json());
app.post('/reports/pdf', async (req, res) => {
const apiKey = process.env.DOCRAPTOR_API_KEY;
if (!apiKey) return res.status(500).json({ error: 'PDF service is not configured.' });
try {
const html = String(req.body.html ?? '');
if (!html) return res.status(400).json({ error: 'Provide html.' });
const upstream = await fetch('https://api.docraptor.com/docs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_credentials: { username: apiKey },
doc: { document_content: html, name: 'report.pdf', type: 'pdf', test: true }
})
});
if (!upstream.ok) {
const details = await upstream.text();
console.error('DocRaptor error', upstream.status, details);
return res.status(502).json({ error: 'PDF generation failed.' });
}
const bytes = Buffer.from(await upstream.arrayBuffer());
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="report.pdf"',
'Content-Length': String(bytes.length)
});
return res.send(bytes);
} catch (error) {
console.error('PDF route failed', error);
return res.status(502).json({ error: 'PDF generation failed.' });
}
});
app.listen(3000);
In production, validate and authorize the caller, constrain document size, and avoid logging sensitive HTML or credentials. If you generate PDFs for user-controlled HTML, treat that input as untrusted and review your application’s access and data-handling requirements.
6. Submit a source URL instead of HTML
When a page is hosted and accessible to DocRaptor, set document_url in the document object in place of document_content. Keep the same binary response handling:
const requestBody = {
user_credentials: { username: process.env.DOCRAPTOR_API_KEY },
doc: {
document_url: 'https://example.com/report',
name: 'report.pdf',
type: 'pdf',
test: true
}
};
A URL-based document is convenient when the rendered page already exists, but it must be fetchable by the service. For HTML content with relative CSS or images, provide a base URL using the documented prince_options.baseurl setting or make asset references absolute.
7. Decide whether JavaScript rendering is needed
DocRaptor documents JavaScript processing as disabled by default. Static HTML and CSS generally do not need it. Enable the appropriate documented JavaScript setting only if your document depends on JavaScript-generated content, such as a chart rendered after page load.
The API reference describes DocRaptor’s JavaScript engine and Prince’s separate JavaScript engine. Both are off by default; enabling both may run code twice. DocRaptor generally recommends its own engine for common JavaScript support. Prince’s engine is for use cases requiring Prince-specific capabilities. Verify the current option names and behavior in the API reference and JavaScript documentation.
8. Choose synchronous, asynchronous, or hosted output
| Mode | What your application receives | When to use it |
|---|---|---|
| Synchronous direct response | PDF bytes in the request response. | Documents that finish within the documented synchronous time limit. |
| Asynchronous | A status identifier, then a result retrieved after processing. | Jobs that may exceed the synchronous limit or should run outside a user-facing request. |
| Hosted output | A URL for the generated document. | When a hosted result URL fits the delivery flow; check current retention and download behavior. |
DocRaptor’s API reference documents a 60-second synchronous generation limit. If a job may take longer, use its asynchronous workflow and poll or retrieve by the returned status identifier according to the current API documentation. Do not assume that a long-running synchronous request will simply wait indefinitely.
Hosted document creation is distinct from direct binary output. The API overview says hosted creation returns a URL. The API reference currently documents hosted test documents as expiring after one day and allowing five downloads; confirm those limits in the live reference before relying on them.
9. Develop with test mode, then switch to production
Set test: true while developing and validating templates. The returned test PDF is watermarked and is not suitable as a production deliverable. DocRaptor’s current API reference says test documents are unlimited across plans and do not count against monthly limits. It also documents the hosted-test download and expiry restrictions noted above. Product terms can change, so check the reference when setting up a production workflow.
When ready to generate real output, remove or set the test option according to the current API reference. Keep the API key in server configuration and use separate configuration values for development and production if your deployment process supports that.
10. Troubleshoot common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| The saved “PDF” contains readable XML or an error message. | The API returned an error status, and the response body was saved as if it were a PDF. | Check response.ok or the HTTP status before writing. Decode and log the body only on the error path. |
| The PDF is unreadable or corrupted. | The HTTP client decoded binary data as text. | Use arrayBuffer() with fetch or Axios responseType: 'arraybuffer'; write the resulting bytes. |
| Stylesheets, images, or fonts are missing. | Relative asset paths have no resolvable base, or assets cannot be reached by the renderer. | Use absolute URLs or configure the documented prince_options.baseurl. Confirm each asset is accessible to the service. |
| The page is blank or missing chart content. | The content depends on JavaScript, which is disabled by default, or the script does not complete as expected. | Enable only the appropriate documented JavaScript engine and check the API’s JavaScript guidance. Avoid enabling both engines unless the use case requires it. |
| The request times out or exceeds the synchronous limit. | The document takes longer than the API’s documented 60-second synchronous window. | Use asynchronous generation and retrieve the result by its status identifier. |
| Test output has a watermark. | The document was generated with test: true. |
Use test mode for development; switch to production mode for final output. |
| The API rejects credentials or the key is missing. | The key was omitted, misconfigured, or exposed through the wrong application layer. | Confirm the server process has the correct secret configured and follow the current API credential format. Never send the secret to the browser. |
| Request examples disagree about fields. | DocRaptor documentation examples use differing request shapes or type fields. | Use the current API reference as the authority and keep the request body aligned with that documented endpoint schema. |
11. Reliability, performance, and cost considerations
- Keep slow work out of short web requests. Synchronous generation has a documented 60-second limit. Use asynchronous generation for potentially long jobs and expose a job status to your application’s caller.
- Preserve the original input. If users need repeatable documents, store or version the source HTML and the relevant template data so a later retry can reproduce the same content.
- Bound work at your application layer. Validate inputs and avoid launching unbounded concurrent render requests from a public endpoint. Pick timeouts and retry behavior based on the current API guidance and your own request lifecycle.
- Keep binary data binary. Avoid unnecessary string conversions or base64 conversions when saving or streaming the PDF; they add work and can corrupt output if handled incorrectly.
- Use test mode for template iteration. DocRaptor’s API reference currently says test documents do not count against monthly limits, but test output is watermarked.
- Check current pricing and terms directly. The research for this guide found DocRaptor’s product page stating a starting price of $15 per month, checked in 2026. Plans and prices may change; confirm them on the vendor’s site before estimating production cost.
12. Or skip the browser setup
If the goal is a screenshot of a web page rather than a paginated PDF document, ScreenshotNeo can return a screenshot or PDF with one GET request. It is a website screenshot API and MCP server from Yorker Media. Cookie banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
For a web page PDF, the request pattern is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -d format=pdf -o page.pdf
See the ScreenshotNeo API documentation for the request options. One thousand screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
13. FAQ
Can I generate a PDF without saving it to disk?
Yes. Read the successful response as bytes and send those bytes from your server with Content-Type: application/pdf. The Express example shows that pattern.
Does DocRaptor need JavaScript enabled for all HTML?
No. Its documentation says JavaScript processing is disabled by default. Enable the relevant engine only when your document relies on JavaScript-generated content.
Can I put the DocRaptor API key in a frontend app?
No. Keep it in server-side configuration or a secret store, and have your backend call DocRaptor.
When should I switch from synchronous to asynchronous generation?
Use asynchronous generation when a document may exceed the documented 60-second synchronous limit or when your application should track rendering as a background job.


