Table of Contents

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:

  1. Page loads. With the default network-idle strategy, the page is also given up to 10 s to reach network idle, so dynamically-injected elements (e.g. cookie consent buttons) exist before your script runs. With an explicit strategy (ForDelay, ForSelector, ForJavaScript, ForSignal), the script runs as soon as the DOM is ready — no implicit wait (1.6.4+; cap or re-enable via WithNetworkIdleTimeout).
  2. Your custom script runs.
  3. The wait strategy condition is evaluated (selector visible, JS expression truthy, signal received, etc.).
  4. 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
    .WithWaitStrategy(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();")
    .WithWaitStrategy(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.
  • WithCustomJS can 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.