Puppeteer and Playwright are browser automation tools that happen to generate PDFs. IronPDF is a purpose-built PDF library designed for production .NET applications.
If you're using Puppeteer-Sharp or Playwright for HTML-to-PDF conversion in .NET, here's why and how to migrate to IronPDF.
Why Migrate from Puppeteer/Playwright to IronPDF?
1. Puppeteer/Playwright Aren't PDF Libraries
Puppeteer and Playwright are browser automation tools designed for:
- End-to-end testing
- Web scraping
- Screenshot capture
- Form automation
PDF generation is a side feature, not the primary focus.
IronPDF is a dedicated PDF library built for:
- HTML-to-PDF conversion
- PDF manipulation (merge, split, forms)
- Digital signatures
- PDF/A compliance
- Production-grade performance
2. Deployment Complexity
Puppeteer-Sharp/Playwright require:
- 300MB+ Chromium browser binaries
- Browser installation on every server
- Managing browser versions
- Platform-specific browser downloads
Docker deployment (Puppeteer-Sharp):
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# Install Chromium and dependencies (huge image size increase)
RUN apt-get update && apt-get install -y \
wget \
gnupg \
ca-certificates \
fonts-liberation \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm1 \
libgcc1 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
xdg-utils \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]
IronPDF Docker deployment:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y \
libc6-dev \
libgdiplus \
libx11-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]
IronPDF's Docker image is 200MB+ smaller.
3. Performance Overhead
Puppeteer/Playwright spawn fresh browser processes for every PDF generation (or maintain heavy browser contexts).
Puppeteer-Sharp example:
using PuppeteerSharp;
// Install via NuGet: Install-Package PuppeteerSharp
for (int i = 0; i < 100; i++)
{
// Launches new browser process each time!
using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
using var page = await browser.NewPageAsync();
await page.SetContentAsync($"<h1>Document {i}</h1>");
await page.PdfAsync($"output-{i}.pdf");
}
Each iteration:
- Spawns browser process (~500ms overhead)
- Initializes browser context
- Renders HTML
- Generates PDF
- Shuts down browser
This is slow for batch processing.
IronPDF reuses the rendering engine:
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer(); // Create once
for (int i = 0; i < 100; i++)
{
var pdf = renderer.RenderHtmlAsPdf($"<h1>Document {i}</h1>");
pdf.SaveAs($"output-{i}.pdf");
}
First render: ~2.8 seconds (Chromium initialization)
Subsequent renders: <1 second each
IronPDF is 5-10x faster for batch operations.
4. API Complexity
Puppeteer/Playwright APIs are verbose and designed for browser automation, not PDF generation.
Playwright example:
using Microsoft.Playwright;
// Install via NuGet: Install-Package Microsoft.Playwright
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new() { Headless = true });
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.SetContentAsync("<h1>Hello World</h1>");
await page.PdfAsync(new()
{
Path = "output.pdf",
Format = "A4",
PrintBackground = true
});
await browser.CloseAsync();
IronPDF equivalent:
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");
80% less code, clearer intent.
5. No PDF Manipulation Features
Puppeteer/Playwright can only generate PDFs. They can't:
- Merge multiple PDFs
- Split PDFs into pages
- Extract text from existing PDFs
- Fill PDF forms
- Add digital signatures
- Apply watermarks to existing PDFs
IronPDF handles all of this:
using IronPdf;
// Install via NuGet: Install-Package IronPdf
// Merge PDFs
var pdf1 = PdfDocument.FromFile("doc1.pdf");
var pdf2 = PdfDocument.FromFile("doc2.pdf");
var merged = PdfDocument.Merge(pdf1, pdf2);
// Extract text
var text = pdf1.ExtractAllText();
// Fill forms
pdf1.Form.SetFieldValue("name", "John Doe");
// Add watermark
pdf1.ApplyWatermark("<h1 style='color: rgba(0,0,0,0.2);'>DRAFT</h1>");
Migration: Puppeteer-Sharp to IronPDF
Step 1: Replace Basic HTML-to-PDF
Before (Puppeteer-Sharp):
using PuppeteerSharp;
// Install via NuGet: Install-Package PuppeteerSharp
var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
var page = await browser.NewPageAsync();
await page.SetContentAsync("<h1>Invoice</h1><p>Total: $500</p>");
await page.PdfAsync("invoice.pdf");
await browser.CloseAsync();
After (IronPDF):
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Invoice</h1><p>Total: $500</p>");
pdf.SaveAs("invoice.pdf");
Benefits:
- No browser lifecycle management
- Faster (no process spawning)
- Cleaner code
Step 2: Replace URL-to-PDF Conversion
Before (Puppeteer-Sharp):
using PuppeteerSharp;
// Install via NuGet: Install-Package PuppeteerSharp
var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
var page = await browser.NewPageAsync();
await page.GoToAsync("https://example.com");
await page.PdfAsync("webpage.pdf");
await browser.CloseAsync();
After (IronPDF):
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
Step 3: Replace Custom Page Options
Before (Playwright):
using Microsoft.Playwright;
// Install via NuGet: Install-Package Microsoft.Playwright
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.SetContentAsync("<h1>Content</h1>");
await page.PdfAsync(new()
{
Path = "output.pdf",
Format = "A4",
Landscape = true,
Margin = new() { Top = "20mm", Bottom = "20mm", Left = "15mm", Right = "15mm" },
PrintBackground = true
});
await browser.CloseAsync();
After (IronPDF):
using IronPdf;
using IronPdf.Rendering;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.MarginLeft = 15;
renderer.RenderingOptions.MarginRight = 15;
renderer.RenderingOptions.PrintHtmlBackgrounds = true;
var pdf = renderer.RenderHtmlAsPdf("<h1>Content</h1>");
pdf.SaveAs("output.pdf");
Strongly-typed options, compile-time validation.
Step 4: Replace Headers and Footers
Before (Puppeteer-Sharp):
using PuppeteerSharp;
// Install via NuGet: Install-Package PuppeteerSharp
var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
var page = await browser.NewPageAsync();
await page.SetContentAsync("<h1>Content</h1>");
await page.PdfAsync(new PdfOptions
{
Path = "output.pdf",
DisplayHeaderFooter = true,
HeaderTemplate = "<div style='font-size: 10px;'>Header</div>",
FooterTemplate = "<div style='font-size: 10px;'><span class='pageNumber'></span> of <span class='totalPages'></span></div>"
});
await browser.CloseAsync();
After (IronPDF):
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
HtmlFragment = "<div style='font-size: 10px;'>Header</div>"
};
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = "<div style='font-size: 10px;'>Page {page} of {total-pages}</div>"
};
var pdf = renderer.RenderHtmlAsPdf("<h1>Content</h1>");
pdf.SaveAs("output.pdf");
IronPDF's merge fields ({page}, {total-pages}) are more intuitive than Puppeteer's class-based approach.
Step 5: Replace JavaScript Execution Wait
Before (Puppeteer-Sharp):
using PuppeteerSharp;
// Install via NuGet: Install-Package PuppeteerSharp
var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
var page = await browser.NewPageAsync();
await page.SetContentAsync(@"
<div id='app'>Loading...</div>
<script>
setTimeout(() => {
document.getElementById('app').innerHTML = '<h1>Loaded!</h1>';
}, 500);
</script>");
await page.WaitForTimeoutAsync(1000); // Wait for JavaScript
await page.PdfAsync("output.pdf");
await browser.CloseAsync();
After (IronPDF):
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var html = @"
<div id='app'>Loading...</div>
<script>
setTimeout(() => {
document.getElementById('app').innerHTML = '<h1>Loaded!</h1>';
}, 500);
</script>";
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.RenderDelay = 1000; // Wait for JavaScript
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
Performance Comparison
Benchmark (100 simple HTML documents):
| Library | Time | Notes |
|---|---|---|
| Puppeteer-Sharp | ~120 seconds | Spawns browser process each time |
| Playwright | ~110 seconds | Similar overhead |
| IronPDF | ~60 seconds | Reuses Chromium engine |
IronPDF is 2x faster for batch operations.
Cost Comparison
| Puppeteer-Sharp/Playwright | IronPDF | |
|---|---|---|
| License cost | Free (Apache 2.0) | $749/developer |
| Docker image size | +500MB (Chromium + deps) | +100MB |
| Deployment complexity | High (browser binaries) | Low (NuGet package) |
| Performance | Moderate (process spawning) | Fast (reused engine) |
| PDF manipulation | ❌ No | ✅ Yes |
| Support | Community (GitHub) | 24/5 commercial support |
IronPDF costs $749 but saves deployment complexity and developer time.
When to Stay with Puppeteer/Playwright
Don't migrate if:
- You're already using Puppeteer/Playwright for testing and PDF generation is a side feature
- Budget is $0 and you can tolerate complexity
- You need advanced browser automation (form filling, screenshots, scraping) alongside PDF generation
Migrate to IronPDF if:
- PDF generation is your primary use case (not browser automation)
- Deployment simplicity matters (smaller Docker images, easier configuration)
- Performance matters (batch processing, high throughput)
- You need PDF manipulation (merge, split, forms, signatures)
- Commercial support is valuable (SLA, guaranteed bug fixes)
Migration Checklist
✅ Audit current Puppeteer/Playwright usage (HTML conversion, screenshots, automation)
✅ If 80%+ is PDF generation → migrate to IronPDF
✅ Install IronPDF via NuGet (Install-Package IronPdf)
✅ Replace browser launch/close with ChromePdfRenderer (reusable)
✅ Migrate PDF options to RenderingOptions
✅ Remove browser binary installation from Docker/deployment
✅ Update Docker images (smaller, simpler)
✅ Performance test with real workloads
✅ License IronPDF for production use
The Bottom Line
Puppeteer and Playwright are browser automation tools that happen to generate PDFs.
IronPDF is a purpose-built PDF library designed for production .NET applications.
If PDF generation is your primary use case:
- IronPDF is 2x faster
- 80% less code
- 200MB+ smaller Docker images
- No browser process management
- Comprehensive PDF manipulation features
- Commercial support
For $749/developer, you eliminate deployment complexity, improve performance, and gain production-grade PDF capabilities.
Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.
Top comments (0)