Custom JavaScript Injection
WithCustomJS lets you run arbitrary JavaScript on the page before the PDF is captured. Common uses include dismissing cookie banners, triggering lazy-loaded content, manipulating the DOM, or signalling CobaltPdf when a complex async render is complete.
How It Works
The script is injected and executed after the page has loaded, but before the configured wait strategy fires. This means:
- Page loads and reaches network idle.
- Your custom script runs.
- The wait strategy condition is evaluated (selector visible, JS expression truthy, signal received, etc.).
- PDF is captured.
Basic Usage
// Remove a cookie banner before capturing
var pdf = await renderer
.WithCustomJS("document.querySelector('.cookie-banner')?.remove();")
.RenderUrlAsPdfAsync("https://example.com");
pdf.SaveAs("clean.pdf");
Async Scripts
Wrap async work in an IIFE. CobaltPdf evaluates the expression and moves on — if you need to wait for async work to complete, use WaitOptions.ForSignal and call window.cobaltNotifyRender() when done.
string js = """
(async () => {
// Wait for charts to finish rendering
await new Promise(resolve =>
setTimeout(resolve, 1000));
// Expand all collapsed sections
document.querySelectorAll('.collapsed').forEach(el => el.click());
})();
""";
var pdf = await renderer
.WithCustomJS(js)
.RenderUrlAsPdfAsync("https://example.com/dashboard");
Combining with ForSignal (Full Control)
When you need to wait for the script itself to complete before capturing, use WaitOptions.ForSignal. CobaltPdf exposes window.cobaltNotifyRender() — call it at the exact moment the page is ready:
string js = """
(async () => {
// Wait for a third-party chart library to signal completion
await new Promise(resolve => {
if (window.Chart && window.Chart.instances.length > 0) {
resolve();
} else {
window.addEventListener('chartsReady', resolve, { once: true });
}
});
// Dismiss cookie consent if present
const btn = document.querySelector('[data-testid="accept-cookies"]');
if (btn) btn.click();
// Tell CobaltPdf the page is ready
window.cobaltNotifyRender();
})();
""";
var pdf = await renderer
.WaitFor(WaitOptions.ForSignal(TimeSpan.FromSeconds(15)))
.WithCustomJS(js)
.RenderUrlAsPdfAsync("https://example.com/complex-dashboard");
Important
When using ForSignal, window.cobaltNotifyRender() must be called — otherwise the render times out and throws a TimeoutException. Add a finally block or fallback setTimeout if the signal might not fire:
// Fallback: signal after 10 s if the event never fires
setTimeout(() => window.cobaltNotifyRender(), 10_000);
Combining with Other Wait Strategies
WithCustomJS can be paired with any wait strategy. The script always runs first; then the wait condition is checked:
// 1. Script runs: removes overlay
// 2. Wait strategy: polls until #report-table appears
var pdf = await renderer
.WithCustomJS("document.querySelector('.loading-overlay')?.remove();")
.WaitFor(WaitOptions.ForSelector("#report-table", TimeSpan.FromSeconds(20)))
.RenderUrlAsPdfAsync("https://example.com/report");
See Wait Strategies for the full list of available strategies.
DOM Manipulation Examples
// Hide a sticky navigation bar that overlaps content
var pdf = await renderer
.WithCustomJS("document.querySelector('nav.sticky')?.style.setProperty('display','none');")
.RenderUrlAsPdfAsync("https://example.com/article");
// Force-expand all `<details>` elements
var pdf2 = await renderer
.WithCustomJS("document.querySelectorAll('details').forEach(d => d.open = true);")
.RenderUrlAsPdfAsync("https://example.com/faq");
// Set a dark-mode class on the body
var pdf3 = await renderer
.WithCustomJS("document.body.classList.add('print-mode');")
.RenderUrlAsPdfAsync("https://example.com/article");
Notes
- The script runs in the page's JavaScript context and has full access to the DOM,
window, and page variables. WithCustomJScan only be called once per render chain. Combine multiple operations into a single script block.- Scripts that throw uncaught exceptions are logged as warnings but do not abort the render.
- CSP-protected pages may block inline script injection. Enable
WithCspBypass()if your script is blocked.