JavaScript execution in HTML-to-PDF conversion determines whether dynamic content renders correctly. Modern web applications use JavaScript extensively — charts from D3.js or Chart.js, data tables from frameworks like React or Vue, dynamic forms, interactive elements. Converting these to PDFs requires a rendering engine that executes JavaScript before [PDF generation](https://ironpdf.com/blog/videos/how-to-generate-html-to-pdf-with-dotnet-on-azure-pdf/), not just parses static HTML.
I've migrated document systems from libraries that ignored JavaScript to Chromium-based solutions that execute it fully. The difference is dramatic. Reports with Chart.js visualizations that appeared blank in wkhtmltopdf-generated PDFs render perfectly with IronPDF's Chromium engine. Dashboards built with React that showed loading spinners instead of data now generate complete PDFs with all content rendered.
The key distinction is between client-side JavaScript libraries (jsPDF, html2pdf.js, pdfmake) and server-side .NET rendering with JavaScript support. Client-side libraries run in browsers, generating PDFs using browser APIs and JavaScript code. Server-side .NET rendering executes JavaScript on the server using headless browser engines, then converts the final rendered state to PDF.
Understanding when each approach fits helps architectural decisions. Client-side libraries work well for user-initiated downloads where the content already exists in the browser — "Save this page as PDF" functionality. Server-side rendering suits automated document generation where you're assembling PDFs in background processes, batch operations, or API endpoints without user browsers involved.
IronPDF's approach is server-side rendering using Chromium's JavaScript engine. HTML containing <script> tags executes during rendering. External JavaScript libraries load from CDNs or embedded sources. Async operations complete before PDF generation if you configure appropriate waits. This creates PDFs from the fully rendered page state, including all JavaScript-generated content.
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var renderer = new [ChromePdfRenderer](https://ironpdf.com/blog/videos/how-to-render-html-string-to-pdf-in-csharp-ironpdf/)();
renderer.RenderingOptions.EnableJavaScript = true;
renderer.RenderingOptions.WaitFor.RenderDelay(2000); // Wait 2 seconds
var html = @"
<html>
<head>
<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>
</head>
<body>
<canvas id='chart' width='400' height='200'></canvas>
<script>
const ctx = document.getElementById('chart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Sales',
data: [12000, 19000, 15000, 22000],
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}]
}
});
</script>
</body>
</html>
";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("chart.pdf");
That's the fundamental pattern — HTML with embedded or external JavaScript, enable JavaScript execution, configure rendering delays for async operations, render to PDF. The chart renders fully before PDF generation, appearing in the final PDF exactly as it would in a browser.
What's the Difference Between Client-Side and Server-Side PDF Generation?
Client-side PDF generation happens in the user's browser using JavaScript libraries like jsPDF, html2pdf.js, or pdfmake. These libraries use browser APIs to draw PDF content or convert DOM elements to PDFs. The PDF generates entirely in JavaScript without server involvement.
I use client-side generation for simple user-initiated downloads — receipts, simple reports, form exports where the data already exists in the browser. The advantages are no server processing (reduces server load) and immediate downloads (no roundtrip). The disadvantages are limited to browser capabilities, inconsistent rendering across browsers, and no control over final output quality.
Server-side PDF generation happens on the server using rendering engines like IronPDF, Playwright, or Puppeteer. The server processes HTML, executes JavaScript, and returns completed PDFs. This works for automated document generation, batch processing, or complex PDFs requiring server-side data assembly.
I use server-side generation for production document workflows — invoice systems processing thousands of PDFs daily, report schedulers generating PDFs overnight, API endpoints that return PDFs for mobile apps. The advantages are consistent output quality (Chromium rendering everywhere), complex data assembly server-side, and support for headless automation. The disadvantage is server resource consumption.
For .NET applications, server-side rendering is the practical choice. You're already running .NET code on servers. Adding IronPDF means HTML-to-PDF conversion happens alongside existing business logic. No need to push PDF generation to browsers with client-side JavaScript libraries.
The confusion arises because searches for "JavaScript PDF generation" return mostly client-side libraries. For .NET developers, those aren't the solution — you need server-side rendering with JavaScript execution support, which IronPDF provides.
How Do I Enable JavaScript Execution During PDF Rendering?
JavaScript execution is enabled by default in IronPDF but you can control it explicitly and configure timing to handle async operations.
Enable JavaScript and set rendering delay:
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.EnableJavaScript = true;
renderer.RenderingOptions.WaitFor.RenderDelay(3000); // Wait 3 seconds
var pdf = renderer.RenderHtmlAsPdf(htmlWithJavaScript);
The rendering delay pauses PDF generation for the specified duration (milliseconds) after page load. This gives JavaScript time to execute, fetch data from APIs, or render dynamic content. I use 1-3 second delays for most JavaScript-heavy pages.
For more precise control, wait for specific elements to appear:
renderer.RenderingOptions.WaitFor.HtmlElement("#content-ready");
This waits until an element with ID content-ready appears in the DOM. Your JavaScript adds this element after all async operations complete:
// After chart renders, add ready indicator
document.getElementById('chart-container').innerHTML += '<div id="content-ready" style="display:none;"></div>';
This signal-based approach is more reliable than fixed delays because it responds to actual content state. If your chart library finishes in 500ms, PDF generation starts then. If it takes 4 seconds due to network conditions, PDF generation waits appropriately.
I use element-based waiting for production systems where rendering timing varies — dashboards querying APIs with variable response times, reports with unpredictable data volumes affecting rendering speed.
What JavaScript Libraries Work with HTML to PDF Conversion?
Most JavaScript charting and visualization libraries work because IronPDF uses Chromium, which supports the same JavaScript APIs as Chrome.
Chart.js for bar charts, line charts, pie charts:
const ctx = document.getElementById('salesChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'Revenue',
data: [45000, 52000, 48000, 61000],
borderColor: 'rgb(75, 192, 192)'
}]
}
});
This renders line charts that appear in PDFs. I use Chart.js for financial reports, sales dashboards, and analytics exports.
D3.js for complex data visualizations:
const data = [30, 86, 168, 234, 155];
d3.select('svg')
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width', d => d)
.attr('height', 50)
.attr('y', (d, i) => i * 60)
.attr('fill', 'steelblue');
D3.js creates SVG-based visualizations that Chromium renders accurately in PDFs. More complex than Chart.js but offers greater customization.
Google Charts for various chart types:
google.charts.load('current', {packages: ['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Task', 'Hours'],
['Work', 11],
['Eat', 2],
['Sleep', 7]
]);
var chart = new google.visualization.PieChart(document.getElementById('piechart'));
chart.draw(data, {title: 'My Daily Activities'});
}
Google Charts requires waiting for the library to load and the chart to render. Use rendering delays or element-based waiting.
DataTables for interactive data tables:
$('#example').DataTable({
data: dataSet,
columns: [
{ title: "Name" },
{ title: "Position" },
{ title: "Office" },
{ title: "Age" }
]
});
DataTables enhances HTML tables with sorting, filtering, and pagination. For PDFs, the enhanced table renders as static HTML — pagination and interactive features don't work in PDFs, but the styled table appears correctly.
How Do I Handle Async JavaScript Operations?
Async JavaScript operations — API calls, timeout-based rendering, promise-based library initialization — require waiting for completion before PDF generation.
Wait for API calls to complete:
var html = @"
<html>
<head>
<script>
async function loadData() {
const response = await fetch('https://api.example.com/sales-data');
const data = await response.json();
document.getElementById('results').innerHTML =
data.map(item => `<div>${item.name}: ${item.value}</div>`).join('');
// Signal completion
document.body.innerHTML += '<div id=\"data-loaded\"></div>';
}
loadData();
</script>
</head>
<body>
<div id='results'>Loading...</div>
</body>
</html>
";
renderer.RenderingOptions.WaitFor.HtmlElement("#data-loaded");
var pdf = renderer.RenderHtmlAsPdf(html);
The fetch completes, data renders, then the signal element appears. IronPDF waits for this signal before generating the PDF, ensuring all data is present.
Handle promise-based library initialization:
Chart.js and other libraries use promises for async operations. Wait for these to resolve:
document.addEventListener('DOMContentLoaded', async () => {
// Wait for library to initialize
await Chart.ready;
// Render chart
new Chart(ctx, config);
// Signal ready
document.body.innerHTML += '<div id=\"charts-ready\"></div>';
});
The DOMContentLoaded event ensures the page structure exists before JavaScript executes. The async/await pattern handles promise resolution. The signal element indicates completion.
I've debugged numerous "blank PDF" issues where async operations didn't complete before PDF generation. The pattern of signal elements solved these consistently — JavaScript explicitly indicates when rendering is complete rather than relying on timing guesses.
What About Single Page Applications (SPAs)?
SPAs built with React, Vue, or Angular require special handling because they render entirely via JavaScript. The initial HTML is minimal, containing only a root div and script tags. The framework populates content after loading.
Render a React application to PDF:
var html = @"
<html>
<head>
<script crossorigin src='https://unpkg.com/react@18/umd/react.production.min.js'></script>
<script crossorigin src='https://unpkg.com/react-dom@18/umd/react-dom.production.min.js'></script>
</head>
<body>
<div id='root'></div>
<script>
const { useState, useEffect } = React;
function App() {
const [data, setData] = useState([]);
useEffect(() => {
// Simulate API call
setTimeout(() => {
setData(['Item 1', 'Item 2', 'Item 3']);
document.body.innerHTML += '<div id=\"react-ready\" style=\"display:none;\"></div>';
}, 1000);
}, []);
return React.createElement('div', null,
React.createElement('h1', null, 'React App'),
React.createElement('ul', null,
data.map((item, i) => React.createElement('li', {key: i}, item))
)
);
}
ReactDOM.render(React.createElement(App), document.getElementById('root'));
</script>
</body>
</html>
";
renderer.RenderingOptions.WaitFor.HtmlElement("#react-ready");
var pdf = renderer.RenderHtmlAsPdf(html);
React renders the component, the useEffect hook runs after mounting, data loads, then the signal element appears. IronPDF waits for the signal, capturing the fully rendered application state.
For production SPA-to-PDF conversion, I typically render the SPA server-side first (using Next.js for React or Nuxt.js for Vue) to generate static HTML, then convert that HTML to PDF. This avoids the complexity of waiting for client-side framework initialization in the PDF rendering engine.
How Do I Debug JavaScript Issues in PDF Rendering?
JavaScript errors during PDF rendering often manifest as blank PDFs or missing content. Debugging requires isolating whether the issue is JavaScript execution or PDF rendering.
Test HTML in a browser first. If JavaScript works in Chrome but fails in the PDF, the issue is rendering configuration (delays, element waiting). If JavaScript fails in Chrome, fix the JavaScript before attempting PDF conversion.
Use console logging from JavaScript (IronPDF captures console output):
console.log('Chart library loaded');
// Chart rendering code
console.log('Chart rendered successfully');
Check IronPDF logs for these messages to confirm JavaScript execution progress.
Enable verbose logging in IronPDF:
IronPdf.Logging.Logger.LoggingMode = IronPdf.Logging.Logger.LoggingModes.All;
IronPdf.Logging.Logger.EnableDebugging = true;
This outputs detailed logs showing JavaScript execution, resource loading, and rendering stages. I use this when diagnosing complex rendering issues.
Common issues I've encountered:
External scripts failing to load: CORS restrictions or network issues prevent script loading. Embed scripts directly or use base64 encoding.
Async operations not completing: Insufficient rendering delay or missing element waiting. Add signal elements or increase delays.
Library versions incompatible: Some JavaScript libraries have compatibility issues with Chromium versions. Test with different library versions or use alternatives.
Quick Reference
| Task | Configuration | Notes |
|---|---|---|
| Enable JavaScript | EnableJavaScript = true |
Default: enabled |
| Fixed delay | WaitFor.RenderDelay(3000) |
Milliseconds |
| Wait for element | WaitFor.HtmlElement("#id") |
More reliable |
| External libraries | Load from CDN or embed | Chart.js, D3.js, etc. |
| Console logging | Check IronPDF logs | Debug JavaScript execution |
| SPA rendering | Wait for app initialization | React, Vue, Angular |
Key Principles:
- IronPDF executes JavaScript server-side using Chromium
- Client-side libraries (jsPDF, html2pdf.js) are different — they run in browsers
- Use rendering delays for async operations (API calls, chart rendering)
- Signal elements are more reliable than fixed delays
- Test HTML in Chrome first to isolate JavaScript vs rendering issues
- Most chart libraries (Chart.js, D3.js, Google Charts) work correctly
- SPAs need careful wait strategies due to delayed rendering
- Blank PDFs usually mean JavaScript didn't complete before PDF generation
The complete JavaScript to PDF tutorial includes advanced scenarios like custom JavaScript execution and message listener configuration.
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)