PDF Generation API vs Browser Print (window.print)
"Just use window.print()" -- it's the first approach most developers reach for when adding print functionality to a web app. It's simple, requires no dependencies, and works in a few lines of code.
But as requirements grow, the cracks appear. Output differs between browsers. Page breaks land in the wrong places. You need to generate PDFs on the server without user interaction. That's when you start looking at PDF generation APIs.
This article compares window.print() with PDF generation APIs using concrete code examples, and gives you a clear framework for deciding which approach fits your project.
How window.print() Works
window.print() is a native browser API that triggers the browser's print dialog for the current page.
function printInvoice() {
window.print();
}
Developers typically pair it with @media print CSS to control the printed output:
@media print {
/* Hide navigation and buttons */
.no-print, nav, .sidebar {
display: none;
}
body {
font-size: 12pt;
color: #000;
}
/* Show link URLs in parentheses */
a[href]::after {
content: " (" attr(href) ")";
}
}
This works well for printing blog posts or simple pages. The problems start when you need production-grade PDFs for business use.
7 Limitations of Browser Printing
1. No Server-Side Generation
window.print() is a client-side API. No browser, no PDF. If you need to auto-generate 1,000 invoices on the first of every month, browser printing simply cannot do it.
2. Inconsistent Rendering Across Browsers
Chrome, Safari, and Firefox each use different rendering engines for print output. The same HTML can produce noticeably different margins, font sizes, and page break positions. "It looked perfect in Chrome but broke in Safari" is a story every frontend developer knows.
3. Unreliable Page Break Control
CSS properties like page-break-before and break-before exist in the spec, but browser implementations are incomplete. Complex layouts -- nested Flexbox, Grid containers -- frequently ignore page break rules. For a deep dive on this, see our CSS page break guide.
4. No Custom Headers or Footers Per Page
Browser printing cannot inject custom headers and footers -- company logos, page numbers, "Confidential" labels -- on each page. The @page rule only supports margin sizing, not arbitrary HTML content.
5. No Password Protection or Watermarks
Securing a PDF with a password or stamping "DRAFT" across every page requires PDF-level features that browsers do not expose through window.print().
6. No Batch Processing
Need to generate 100 quotes at once? With window.print(), each user has to click through the print dialog one at a time. Background bulk generation is not possible.
7. File Size and Quality Vary by Browser
The same page can produce a 2MB PDF in Chrome and a 5MB PDF in Safari. Image compression algorithms and font embedding strategies differ between browsers, giving you no control over the final file.
Comparison Table
| Feature | window.print() | PDF Generation API |
|---|---|---|
| Server-side generation | No | Yes |
| Consistent output | Browser-dependent | Always identical |
| Page break control | Limited | Full control |
| Headers / footers | No | Per-page customization |
| Batch generation | No | Yes |
| Password protection | No | Yes |
| Watermarks | No | Yes |
| Cost | Free | API plan |
| Setup | None | API integration |
| User interaction | Required | Not required |
When to Switch
Use this decision flowchart to figure out which approach your project needs:
Do you need to generate PDFs without user interaction?
├── Yes → PDF Generation API
└── No
├── Do you need identical output across browsers?
│ ├── Yes → PDF Generation API
│ └── No
│ ├── Do you need per-page headers/footers?
│ │ ├── Yes → PDF Generation API
│ │ └── No → window.print() is fine
│ └──
└──
window.print() is enough when:
- You have an internal tool with a simple "Print this page" button
- Minor differences in output between browsers are acceptable
- You don't need to store the PDF on your server
Switch to a PDF generation API when:
- Your system auto-sends invoices or quotes to customers
- You need brand-consistent PDF output across all clients
- Monthly reports require batch generation
- You want to move away from self-hosted Puppeteer
Migration Example: window.print() to API
Let's see how straightforward the migration looks in practice.
Before: Client-side with window.print()
// User clicks button → browser print dialog opens
document.getElementById('print-btn').addEventListener('click', () => {
window.print();
});
After: PDF Generation API call
document.getElementById('download-btn').addEventListener('click', async () => {
const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk-your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: document.getElementById('invoice').innerHTML,
options: { 'page-size': 'A4' }
})
});
const blob = await response.blob();
const url = URL.createObjectURL(blob);
window.open(url);
});
The difference is clear. window.print() opens the browser's print dialog and hopes for the best. The API returns a controlled, consistent PDF file. Call the same API from your server, and you get batch generation without any user interaction.
See implementation examples in multiple languages in our quickstart guide.
Real-World Scenarios: Which Should You Choose?
Abstract comparisons only go so far. Let's walk through specific business scenarios to make the choice concrete.
Scenario 1: Automated Invoice Generation
An e-commerce platform needs to generate PDF invoices on order completion and email them to customers.
With window.print(): Impossible. It requires browser interaction, so automated server-side generation and email attachment cannot work. An admin would need to manually print each invoice one by one.
With a PDF generation API:
// Runs server-side on order completion
async function generateInvoice(order) {
const html = renderInvoiceTemplate(order);
const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
html,
options: { 'page-size': 'A4' }
})
});
const pdfBuffer = await response.arrayBuffer();
await sendEmailWithAttachment(order.email, pdfBuffer);
}
Scenario 2: Monthly Report Batch Generation
Generate usage reports for all customers on the first of every month.
With window.print(): Impossible. Someone would need to open 100 browser tabs and manually print each report.
With a PDF generation API:
// Cron job runs on the 1st of each month
async function generateMonthlyReports() {
const customers = await getActiveCustomers();
for (const customer of customers) {
const data = await getUsageData(customer.id);
const html = renderReportTemplate(customer, data);
const pdf = await generatePdf(html, {
'page-size': 'A4',
'header-html': renderHeader(customer.name),
'footer-html': renderFooter()
});
await uploadToStorage(pdf, `reports/${customer.id}/${getMonth()}.pdf`);
await notifyCustomer(customer, pdf);
}
}
Scenario 3: Certificate Issuance
Issue a personalized completion certificate PDF immediately when someone finishes an online course.
With window.print(): Technically possible, but unreliable. Font rendering and layout differ between browsers, so the certificate quality is inconsistent. If the user forgets to click "Print," no PDF is saved.
With a PDF generation API: The server generates a consistent, high-quality PDF and provides a download link. The generation event is recorded in the database simultaneously. See certificate automation for more details.
Scenario 4: Printing an Internal Help Page
A simple "print this page" button on an internal wiki.
With window.print(): This is the right choice. Output quality differences between browsers are acceptable, and no server-side storage is needed. A few @media print CSS rules to hide navigation elements are all you need.
The key takeaway: If PDF quality and consistency don't matter and users are manually initiating the action, window.print() is simpler and perfectly adequate.
Server-Side Migration Examples (Multiple Languages)
One of the biggest advantages of switching to a PDF generation API is calling it from any backend language.
Node.js (Express)
const express = require('express');
const app = express();
app.post('/api/invoices/:id/pdf', async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
const html = renderTemplate('invoice', invoice);
const pdfResponse = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ html, options: { 'page-size': 'A4' } })
});
const pdf = await pdfResponse.arrayBuffer();
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="invoice-${invoice.number}.pdf"`);
res.send(Buffer.from(pdf));
});
Python (Flask / Django)
import requests
def generate_invoice_pdf(invoice):
html = render_template('invoice.html', invoice=invoice)
response = requests.post(
'https://pdf.funbrew.cloud/api/pdf/generate',
headers={
'Authorization': f'Bearer {os.environ["PDF_API_KEY"]}',
'Content-Type': 'application/json',
},
json={
'html': html,
'options': {'page-size': 'A4'}
}
)
return response.content # PDF bytes
PHP (Laravel)
use Illuminate\Support\Facades\Http;
public function downloadInvoicePdf(Invoice $invoice)
{
$html = view('pdf.invoice', compact('invoice'))->render();
$response = Http::withToken(config('services.pdf.key'))
->post('https://pdf.funbrew.cloud/api/pdf/generate', [
'html' => $html,
'options' => ['page-size' => 'A4'],
]);
return response($response->body())
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', "attachment; filename=\"invoice-{$invoice->number}.pdf\"");
}
For more language examples including TypeScript, Go, and Ruby, see our quickstart by language guide.
10 @media print CSS Pitfalls
Even if you stick with window.print(), knowing these CSS gotchas will save you hours of debugging.
1. Flexbox and Grid Break in Print Mode
Many browser print engines render Flexbox and CSS Grid differently than on screen. You may need to fall back to simpler layouts for print.
@media print {
.flex-container {
display: block; /* Disable Flexbox */
}
.grid-layout {
display: block; /* Disable Grid */
}
.grid-layout > * {
width: 100%;
margin-bottom: 1rem;
}
}
2. Background Colors Not Printed by Default
Browsers skip background colors by default to save ink. You can force them with -webkit-print-color-adjust, but support is inconsistent.
@media print {
.highlight-box {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
background-color: #fff3cd !important;
}
}
3. position: fixed Repeats on Every Printed Page
position: fixed elements like sticky headers appear on every page in print output (especially in Chrome), cluttering the PDF.
@media print {
.fixed-header, .sticky-nav {
position: static; /* Remove fixed positioning */
}
}
4. box-shadow Wastes Ink and Slows Rendering
Decorative shadows that work well on screen become unnecessary ink consumption and processing overhead in print.
@media print {
* {
box-shadow: none !important;
}
}
5. vh and vw Units Don't Match Page Dimensions
100vh refers to the browser viewport height, not the printed page height. For A4 paper, the actual page height is approximately 297mm.
@media print {
.full-page-section {
height: auto; /* Remove vh */
page-break-inside: avoid;
}
}
6. iframe Content Doesn't Print
window.print() only targets the current document. Content inside iframes -- dynamically embedded charts, widgets, third-party components -- will be blank or missing in the print output.
7. CSS Animations Freeze at the Wrong State
Elements can be captured mid-animation. A fade-in animation starting at opacity: 0 might cause the element to be invisible in the printed output.
@media print {
* {
animation: none !important;
transition: none !important;
opacity: 1 !important;
}
}
8. Web Fonts May Not Load for Print
External fonts from Google Fonts or other CDNs may fall back to system fonts during printing, especially in offline environments or slow network conditions.
@media print {
body {
font-family: "Noto Sans", "Helvetica Neue", Arial, sans-serif;
}
}
9. display: none Behaves Differently in Preview vs Actual Print
Elements hidden with @media print { display: none } may appear in print preview but not in the actual output, or vice versa. This is a browser-specific bug, so always test with actual PDF output rather than relying on the print preview screen.
10. Tables Split Across Page Boundaries
Long tables get cut in the middle of rows, and headers don't carry over to the next page by default.
@media print {
table { page-break-inside: auto; }
tr { page-break-inside: avoid; }
thead { display: table-header-group; } /* Repeat header on each page */
}
For a comprehensive look at all CSS print issues -- page breaks, @page rules, font handling, and more -- read our HTML to PDF CSS snippets guide.
Migration Checklist
Use this checklist when planning your move from window.print() to a PDF generation API.
Before Migration
- Identify all places in your codebase that use
window.print() - Document the PDF requirements for each use case (quality, batch, automation)
- Review existing
@media printCSS to determine what can be reused with the API - Decide whether you need server-side generation or client-side API calls
During Migration
- Create PDF templates in HTML (you can reuse existing
@media printCSS) - Store API keys in environment variables (never hardcode)
- Implement error handling (fallback for API outages)
- Decide where generated PDFs will be stored (S3, local storage, etc.)
After Migration
- Compare output quality between browser print and API-generated PDFs
- Test with multiple templates
- Verify batch processing performance
- Confirm error handling and fallback behavior
For a detailed migration walkthrough, see our Puppeteer to PDF API migration guide.
Performance and Cost Comparison
Generation Speed
| Method | First Generation | Subsequent | Batch (100 docs) |
|---|---|---|---|
| window.print() | Instant (dialog) | Instant | Not possible |
| PDF Generation API | 1-3 seconds | 1-3 seconds | Minutes (parallelizable) |
| Puppeteer (self-hosted) | 2-5 seconds | 1-3 seconds | Depends on browser memory |
Operating Cost
| Method | Infrastructure | Dev Cost | Maintenance |
|---|---|---|---|
| window.print() | None | Low (CSS only) | Medium (breaks on browser updates) |
| PDF Generation API | None (managed) | Low (API integration) | Low |
| Puppeteer (self-hosted) | Server required | High (Chrome management) | High (memory leaks, etc.) |
For a detailed comparison with Puppeteer and other libraries, see PDF API vs Library Comparison.
Frequently Asked Questions
My window.print() output quality is fine. Do I still need to switch to an API?
If quality meets your requirements and you don't need server-side generation or batch processing, window.print() is perfectly fine. Consider switching only when automation or consistency requirements emerge.
Can a PDF generation API use my existing HTML and CSS?
Yes. FUNBREW PDF uses a Chromium-based rendering engine, so you can pass the same HTML and CSS that your browser displays. Your @media print styles will be applied as well.
Can I use window.print() and a PDF API together?
Absolutely. A hybrid approach is common: keep window.print() for user-initiated printing and use the API only for automated server-side generation. This minimizes migration effort while gaining automation capabilities.
Can I fall back to window.print() if the API is down?
If you're calling the API from the client side, you can implement a fallback to window.print() on error. Note that output quality may differ, so you may want to inform the user.
Do I still need @media print CSS when using a PDF API?
Not strictly, but you can leverage it. If your HTML includes @media print styles, the API will apply them. You can also write API-specific CSS separately.
What's the best practice for batch generating large volumes of PDFs?
Control parallelism and implement proper retry logic. For 1,000+ documents, combine with a queue system (Redis, SQS, etc.) to stay within rate limits. See our production PDF guide for details.
Summary
window.print() is a simple, powerful tool. If your only requirement is "let the user print this page," nothing else is needed.
But if any of these apply to your project, it's time to consider a PDF generation API:
- Server-side auto-generation
- Consistent output across browsers
- Page numbers, headers, and footers
- Password protection or watermarks
- Batch processing and bulk generation
FUNBREW PDF lets you generate PDFs server-side using the same HTML and CSS you already have. Keep your existing frontend assets and go beyond the limits of window.print().
Related Articles
- HTML to PDF CSS Techniques -- Page breaks, @page rules, and font fixes
- HTML to PDF Complete Guide -- Compare all conversion methods
- Puppeteer to PDF API Migration -- Moving from self-hosted to API
- HTML to PDF Troubleshooting -- Common errors and solutions
- wkhtmltopdf vs Chromium -- Engine comparison