In this tutorial, we’ll build a small web application that allows users to:
- Enter a webpage URL
- Preview the page (optional)
- Download it as a PDF
- Choose PDF options like page size and orientation
We’ll use Node.js, Express, Puppeteer, and some HTML/CSS/JS. for this example, but its just as easy to implement in other languages and environments.
Step 1: Project Setup
Create a new folder and initialize a Node.js project
mkdir puppeteer-pdf-downloader
cd puppeteer-pdf-downloader
npm init -y
We are using ES Modules in this example so add "type": "module", to your package.json file
{
"name": "puppeteer-pdf-downloader",
"version": "1.0.0",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"body-parser": "^1.20.2",
"puppeteer": "^21.4.0"
}
}
Next install our dependencies
npm install express body-parser puppeteer
Step 2: Create the Frontend (index.html)
For our basic example we will create index.html file in our project root to house the ui for the downloader.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PDF Downloader with Preview</title>
<style>
/* General styling for a clean, modern UI */
* { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: #f5f6fa; margin:0; padding:0; display:flex; flex-direction:column; align-items:center; }
h1 { margin-top:30px; color:#2f3640; }
form { background-color:#fff; padding:20px 25px; border-radius:12px; box-shadow:0 4px 12px rgba(0,0,0,0.1); margin:20px 0; display:flex; flex-direction:column; gap:12px; width:90%; max-width:500px; }
input, select { padding:10px 12px; border:1px solid #dcdde1; border-radius:8px; font-size:16px; width:100%; }
button { padding:12px; border:none; border-radius:8px; font-size:16px; font-weight:bold; cursor:pointer; transition:background 0.3s; }
button[type="submit"] { background-color:#0097e6; color:white; }
button[type="submit"]:hover { background-color:#40739e; }
button#downloadBtn { background-color:#44bd32; color:white; }
button#downloadBtn:hover { background-color:#4cd137; }
h2 { color:#2f3640; margin-bottom:10px; }
iframe { border:1px solid #dcdde1; border-radius:12px; width:90%; max-width:900px; height:500px; margin-bottom:40px; }
label { font-weight:bold; margin-right:8px; }
</style>
</head>
<body>
<h1>Webpage to PDF Downloader</h1>
<form id="pdfForm">
<input type="text" id="url" placeholder="Enter webpage URL" required>
<div style="display:flex; gap:10px; flex-wrap: wrap;">
<div>
<label for="format">Page Size:</label>
<select id="format">
<option value="A4">A4</option>
<option value="Letter">Letter</option>
</select>
</div>
<div>
<label for="orientation">Orientation:</label>
<select id="orientation">
<option value="portrait">Portrait</option>
<option value="landscape">Landscape</option>
</select>
</div>
</div>
<div style="display:flex; gap:10px; margin-top:10px;">
<button type="button" id="previewBtn">Preview Page</button>
<button type="button" id="downloadBtn">Download PDF</button>
</div>
</form>
<h2>Preview:</h2>
<iframe id="preview"></iframe>
<script>
// Elements
const previewBtn = document.getElementById('previewBtn');
const downloadBtn = document.getElementById('downloadBtn');
const preview = document.getElementById('preview');
const urlInput = document.getElementById('url');
const formatSelect = document.getElementById('format');
const orientationSelect = document.getElementById('orientation');
// Helper: Get URL and PDF options
const getCurrentOptions = () => ({
url: urlInput.value,
options: {
format: formatSelect.value,
landscape: orientationSelect.value === 'landscape'
}
});
// Preview button click
previewBtn.addEventListener('click', () => {
const { url } = getCurrentOptions();
if (!url) return alert('Please enter a URL first');
preview.src = url; // Load webpage in iframe for preview
});
// Download button click
downloadBtn.addEventListener('click', async () => {
const { url, options } = getCurrentOptions();
if (!url) return alert('Please enter a URL first');
try {
const response = await fetch('/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, options })
});
if (!response.ok) throw new Error('Failed to download PDF');
const blob = await response.blob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'page.pdf';
link.click();
} catch (err) {
alert(err.message);
}
});
</script>
</body>
</html>
The index.html file serves as the frontend interface for the PDF downloader. It provides an input field where users can enter the URL of the webpage they want to convert, along with dropdowns to select PDF page size (A4 or Letter) and orientation (portrait or landscape). Users can optionally click the Preview Page button to load the webpage in an iframe, allowing them to see it before generating a PDF.
The Download PDF button sends a POST request to the backend (server.js) using fetch, including the URL and PDF options as JSON. The server receives this data, uses Puppeteer to render the page in a headless browser, generates a PDF according to the selected options, and returns it as a downloadable file.
The frontend then creates a temporary link from the response blob, automatically triggering the download, making the process seamless for the user.
Step 3: Backend (server.js)
Create server.js in our project root
import express from 'express';
import bodyParser from 'body-parser';
import puppeteer from 'puppeteer';
const app = express();
// Middleware
app.use(bodyParser.json());
app.use(express.static('.')); // Serve index.html
// Endpoint to generate PDF
app.post('/download', async (req, res) => {
const { url, options } = req.body;
if (!url) return res.status(400).send('URL is required');
try {
// Launch Puppeteer
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Navigate to the URL
await page.goto(url, { waitUntil: 'networkidle0' });
// Generate PDF with options
const pdfBuffer = await page.pdf({
format: options?.format || 'A4',
landscape: options?.landscape || false,
printBackground: true, // Include styles and images
margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' }
});
await browser.close();
// Send PDF as response
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="page.pdf"',
});
res.send(pdfBuffer);
} catch (err) {
console.error(err);
res.status(500).send('Error generating PDF');
}
});
// Start server
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
The server.js file acts as the backend of the PDF downloader. It uses Express to serve the index.html frontend and to handle POST requests at the /download endpoint. When the frontend sends a URL and PDF options, the server launches Puppeteer, which opens a headless browser and navigates to the specified webpage.
Once the page fully loads, Puppeteer generates a PDF with the requested settings, including page size, orientation, and background styles. The PDF is then sent back to the frontend as a binary response with proper headers so the browser recognizes it as a downloadable file.
This setup allows the frontend to remain lightweight while Puppeteer handles the heavy lifting of rendering and converting webpages into high-quality PDFs.
Step 4: Run the our app
In the terminal run the below
node server.js
now open your browser and type
http://localhost:3000
You should see the the PDF Downloader form below
Next add a url to the input field and check that you can change options, and preview. Once you're happy click the 'Download PDF' button, wait a couple of seconds and the Download dialog will appear. Give your file a name, choose file destination and and click Save.
In this tutorial, we built a simple webpage to PDF downloader using Puppeteer, Express, and a styled frontend. Users can enter a URL, optionally preview it, select PDF options, and download it immediately. This is a basic example, but you can explore Puppeteer’s documentation to add more advanced features such as custom margins, page numbers, scaling and more

Top comments (0)