- HTML to PDF APIConvert HTML and URLs into PDFsExtract PDF Form Data APIEasily extract data from PDFsWatermark PDF APIAdd custom watermarks to PDFsProtect PDF APISecure your PDFs with passwordCompress PDF APIReduce file size without losing qualityFlatten PDF APIFlatten PDFs to make form permanentDigital Signature APISend documents for signing
- API DocumentationAPI DocumentationFull REST API referenceNodeJS SDKClient library for Node.jsJava SDKClient library for JavaC# SDKClient library for C#PHP SDKClient library for PHPPython SDKClient library for Python
- Pricing
- Contact Us
How to Wait for Dynamic Content Before Generating a PDF
Learn how to capture charts, single-page apps, and lazy-loaded data only after they finish rendering.
Overview
Charts, React or Vue apps, and pages that fetch data after loading often come out blank or half-rendered in a PDF. The page has loaded, but its JavaScript hasn't finished drawing yet, so the PDF captures an empty canvas or a loading spinner.
This guide shows how to use the HTML to PDF API options waitForSelector, waitForNetworkIdle, javascript, and delay to capture the page only after it has finished rendering.
Choosing a strategy
Every request already waits for the page's load and DOMContentLoaded events and for the network to settle (no more than 2 open connections). Content that renders after that point needs one of these options:
waitForSelector: Waits for an element to appear. Best when you control the page.waitForNetworkIdle: Waits until there are no open network connections. Best for pages you don't control.javascript: Runs your code in the page before capture, e.g. to turn off animations.delay: Waits a fixed number of milliseconds. Use it as a last resort.
The API applies them in this order:
- Load the page (plus
waitForNetworkIdle, if set). - Wait for
waitForSelector. - Run click actions such as
clickSelectorChainSetup, if set. - Inject
javascript, thencss. - Wait for
delay. - Wait for images if
loadImagesis set, then generate the PDF.
waitForSelector
waitForSelector pauses until an element matching a CSS selector exists in the DOM. It is the most precise option: the PDF is generated as soon as the element appears, with no guessing. In this example, the API waits for a #chart-ready element before capturing the page.
url: The URL of the page you want to convert.waitForSelector: The CSS selector to wait for.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/reports/sales",7 "waitForSelector": "#chart-ready"8 }' \9 -o output.pdfThe selector must appear within 30 seconds. If it doesn't, the request fails with a 422 error instead of returning an incomplete PDF.
Signal "ready" from your page
Waiting for the chart's own canvasor a table's container isn't enough, because those elements usually exist before they are filled in. The reliable pattern is to have the page add a marker element or attribute once rendering is done, then point waitForSelectorat it. The marker doesn't need to be visible; it only has to exist in the DOM.
Chart.js example
Add the marker in the chart's animation.onComplete callback, then send "waitForSelector": "#chart-ready". This works the same whether you send the page with url or as raw html.
1<canvas id="sales-chart"></canvas>23<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>4<script>5 new Chart(document.getElementById("sales-chart"), {6 type: "bar",7 data: {8 labels: ["Q1", "Q2", "Q3", "Q4"],9 datasets: [{ label: "Sales", data: [120, 190, 150, 210] }],10 },11 options: {12 animation: {13 // Called when the chart has finished drawing14 onComplete: function () {15 if (!document.getElementById("chart-ready")) {16 const marker = document.createElement("div");17 marker.id = "chart-ready";18 document.body.appendChild(marker);19 }20 },21 },22 },23 });24</script>React example
Render a data-pdf-ready attribute only after the data has loaded, then send "waitForSelector": "[data-pdf-ready]". In Vue, use the same attribute on an element guarded by v-if.
1function SalesReport() {2 const [data, setData] = useState(null);34 useEffect(() => {5 fetch("/api/sales")6 .then((res) => res.json())7 .then(setData);8 }, []);910 if (!data) return <p>Loading…</p>;1112 // The attribute only exists once the real content is rendered13 return (14 <div data-pdf-ready="true">15 <SalesTable rows={data.rows} />16 </div>17 );18}If a page has several charts or data sources, add the marker only when all of them are done.
waitForNetworkIdle
When you can't change the page, waitForNetworkIdle is the next best option. It makes the page load wait until there are no open network connections, so data fetched after the initial load has time to arrive.
waitForNetworkIdle: Set totrueto wait for zero network connections.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/dashboard",7 "waitForNetworkIdle": true8 }' \9 -o output.pdfKeep in mind:
- Pages that poll an API, stream analytics, or keep a WebSocket or long-polling connection open may never go idle. The request then fails once
timeoutis reached, so set a lowertimeoutto fail fast. - A quiet network doesn't mean rendering is finished. Chart animations and client-side rendering can continue after the last request completes.
Inject JavaScript
javascript injects a script into the page just before capture. Use it to put the page into a print-ready state, for example by finishing chart animations immediately. This example turns off animations on every Chart.js chart on the page so they render in their final state.
javascript: The JavaScript code to run in the page.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/reports/sales",7 "javascript": "Object.values(Chart.instances).forEach(function (chart) { chart.options.animation = false; chart.update(\"none\"); });"8 }' \9 -o output.pdfThe script runs after waitForSelector, so an element your script creates can't be used as the selector to wait for. The API also doesn't wait for asynchronous work your script starts. If the script triggers new content, such as scrolling to load more items, pair it with delay:
1{2 "url": "https://example.com/products",3 "javascript": "window.scrollTo(0, document.body.scrollHeight);",4 "delay": 20005}For lazy-loaded images specifically, set loadImages to true instead. It scrolls through the page and waits for images to finish loading.
delay
delay waits a fixed number of milliseconds before generating the PDF. The maximum is 20000 (20 seconds).
delay: The time to wait, in milliseconds.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/dashboard",7 "delay": 30008 }' \9 -o output.pdfA fixed delay is a guess. If your data source is slow, the PDF is still incomplete; if it's fast, every request waits longer than it needs to. Prefer waitForSelector when you can, and use delayonly for pages you can't change and that never go network-idle.
Additional notes
timeoutlimits how long the page may take to load, in milliseconds. The default and maximum is900000(15 minutes). It covers thewaitForNetworkIdlewait but notwaitForSelector, which always has its own 30-second limit.- You can combine options. For example,
waitForNetworkIdleto let data arrive, thenjavascriptto disable animations. Each one adds to the total generation time. - To control how the rendered content is laid out on the page, see Page size and margins.
For a full list of supported options, see the HTML to PDF API Reference.