ScreenshotNeo

BlogHTML to image & PDF

How to Make DocRaptor Wait for JavaScript Before Rendering a PDF

Enable DocRaptor’s JavaScript engine, then use docraptorJavaScriptFinished() to wait for asynchronous page content before PDF rendering.

By the ScreenshotNeo team4 October 20268 min read

Direct answer: Enable one JavaScript engine in your DocRaptor request. If DocRaptor’s usual completion detection runs before your asynchronous content is ready, define docraptorJavaScriptFinished() in the HTML being converted. Return false while the required content is pending and true when it is ready.

For most pages, use DocRaptor’s standard JavaScript engine with javascript: true. The separate Prince engine uses prince_options[javascript]: true. JavaScript is disabled by default, and enabling both engines can run your page code twice. See the DocRaptor API documentation and its pages on JavaScript rendering and delaying conversion for the current request details.

1. Enable JavaScript in the PDF request

Here is a complete cURL request using the standard engine. Set YOUR_API_KEY in your shell environment first. The request sends HTML to DocRaptor and saves the returned PDF.

export DOCRAPTOR_API_KEY="YOUR_API_KEY"
curl --user "$DOCRAPTOR_API_KEY:" \
  --header "Content-Type: application/json" \
  --data '{"document_content":"<html><body><h1>PDF content</h1></body></html>","name":"report.pdf","document_type":"pdf","javascript":true}' \
  https://api.docraptor.com/docs \
  --output report.pdf

For a real page, include the readiness function in the HTML passed as document_content, or in the source HTML your request asks DocRaptor to render. Keep the API key on a server. DocRaptor’s tutorial warns that putting a live key in browser-side code on a publicly accessible page exposes it in the page source. Its test mode can help verify a request; test documents are watermarked. See the official tutorial.

2. Report readiness from the page

Define the hook in the page context. It should return a boolean: false until the exact content needed in the PDF is ready, then true.

<script>
  docraptorJavaScriptFinished = function () {
    return document.getElementById("content-needed-in-pdf") !== null;
  };
</script>

This simple example works when the element appears only after its content is ready. If your application inserts the element first and fills it later, test for a more meaningful signal, such as a completed data attribute or a non-empty chart container. The hook runs in the page context and can inspect the DOM or other page state. A value other than true or false is treated as an error.

Example: wait for asynchronous content

This illustrative page creates its report content asynchronously and exposes a readiness flag only after the update completes:

<div id="report" data-ready="false"></div>
<script>
  const report = document.getElementById("report");
  docraptorJavaScriptFinished = function () {
    return report.dataset.ready === "true";
  };

  fetch("/report-data")
    .then(response => response.json())
    .then(data => {
      report.textContent = data.summary;
      report.dataset.ready = "true";
    });
</script>

Use a readiness signal tied to the output. If the page has several independently loaded components, return true only when all required components are ready. For example, track each chart or data panel and check that every required item has completed.

Fixed delay as a fallback

DocRaptor also documents a fixed-wait pattern. It can help when an application has a known, stable delay but cannot expose a state-based signal. It is not a guarantee that the chosen interval will work under every network or server condition.

<script>
  let didWait = false;
  docraptorJavaScriptFinished = function () {
    if (didWait) return true;
    setTimeout(function () { didWait = true; }, 3000);
    return false;
  };
</script>

Choose the shortest delay that reliably covers the specific work, and prefer an explicit completion flag where possible. A longer fixed delay makes every conversion wait even when the page is already ready.

3. Choose the right JavaScript engine

Engine Request option When it fits
Standard DocRaptor engine javascript: true Recommended for most users and designed for popular JavaScript tools and libraries such as React, Adobe Typekit, and Highcharts.
Prince JavaScript engine prince_options[javascript]: true Consider when you need Prince-specific PDF scripting features, including JavaScript from CSS, multi-pass rendering, or PDF box access.

These engines are distinct. Prince is not a web browser and may behave differently from modern browsers. Check compatibility with the JavaScript your page uses and whether you need Prince’s PDF-specific capabilities. The DocRaptor documentation recommends the standard engine for most users. Do not enable both unless you deliberately want the page code to execute twice.

4. Python and Node.js request examples

The following examples send HTML content and enable the standard engine. Store the API key in an environment variable; do not put a live key in front-end code.

Python

import os
import requests

api_key = os.environ["DOCRAPTOR_API_KEY"]
html = """<html><body>
<div id='content-needed-in-pdf'>Ready</div>
<script>
  docraptorJavaScriptFinished = function () {
    return document.getElementById('content-needed-in-pdf') !== null;
  };
</script>
</body></html>"""

response = requests.post(
    "https://api.docraptor.com/docs",
    auth=(api_key, ""),
    json={
        "document_content": html,
        "name": "report.pdf",
        "document_type": "pdf",
        "javascript": True,
    },
    timeout=90,
)
response.raise_for_status()
with open("report.pdf", "wb") as pdf:
    pdf.write(response.content)

Node.js

const apiKey = process.env.DOCRAPTOR_API_KEY;
if (!apiKey) throw new Error("Set DOCRAPTOR_API_KEY first");

const html = `<html><body>
<div id="content-needed-in-pdf">Ready</div>
<script>
  docraptorJavaScriptFinished = function () {
    return document.getElementById("content-needed-in-pdf") !== null;
  };
</script>
</body></html>`;

const response = await fetch("https://api.docraptor.com/docs", {
  method: "POST",
  headers: {
    "Authorization": `Basic ${Buffer.from(`${apiKey}:`).toString("base64")}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    document_content: html,
    name: "report.pdf",
    document_type: "pdf",
    javascript: true,
  }),
});
if (!response.ok) {
  throw new Error(`DocRaptor returned ${response.status}: ${await response.text()}`);
}
const pdf = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(fs => fs.writeFile("report.pdf", pdf));

5. JavaScript readiness and other request limits

Keep these controls separate; they solve different problems.

  • Completion hook: docraptorJavaScriptFinished() tells DocRaptor whether the page’s JavaScript-driven output is ready.
  • Resource timeout: DocRaptor documents an external HTTP resource timeout that defaults to 10 seconds and can be set from 1 to 60 seconds. This controls resource fetching, not the page readiness hook.
  • Request duration: synchronous requests have a documented 60-second hard limit; asynchronous requests have a 600-second limit. Async creation and retrieval can accommodate a longer-running document, but do not replace the readiness function.

Consult the API reference for the request fields and the documentation on asynchronous documents for the async flow. Limits and endpoint requirements should be checked against your account and current API documentation.

6. Troubleshooting missing or incomplete content

Symptom Likely cause Fix
JavaScript-generated content is absent JavaScript is disabled, or the request enabled the wrong engine option. Enable one engine explicitly. Start with javascript: true for the standard engine.
Content is still missing even though JavaScript is enabled Asynchronous requests often do not finish before rendering by default. Define docraptorJavaScriptFinished() and check the actual required output state.
The hook errors or rendering fails The function returns a value other than boolean true or false, or refers to unavailable page state. Ensure every path returns a boolean and guard access to elements or state that may not exist yet.
A chart appears incomplete or animated Chart animation can continue while the PDF is being rendered. Disable chart animations in the PDF rendering path and make the readiness signal wait for chart completion.
Chart characters or symbols are missing Encoding or font/resource handling may differ in the PDF environment. Use UTF-8 where needed and verify that the required fonts and resources can load.
The request times out The synchronous hard limit, resource loading, or a readiness condition that never becomes true may be responsible. Check the hook and network resources, distinguish the 1–60 second resource timeout from the request limit, and use asynchronous document creation for work that needs more time.
JavaScript appears to run twice Both the standard and Prince engines may be enabled. Choose the single engine that fits the page and required PDF behavior.
Generation stops after a console message Default console-message handling varies by pipeline: Pipelines 1–6 halt generation by default, while Pipeline 7 and later ignore and log messages by default. Check which pipeline behavior applies to your account and inspect the rendered page’s console output.

These rendering issues and remedies are covered in DocRaptor’s troubleshooting documentation.

7. Reliability, performance, and cost considerations

  • Prefer state over time: a content-based condition avoids waiting longer than needed and avoids assuming every render finishes within one fixed interval.
  • Keep the condition attainable: if a request fails or a component never sets its ready flag, a hook that waits forever can prevent useful output. Handle failed data loads in the page and decide whether an error state should itself be printable.
  • Reduce work during PDF capture: disable animations that do not belong in a static document and avoid waiting for nonessential widgets.
  • Watch external resources: fonts, images, scripts, and data endpoints must be accessible to the rendering service. Increasing a resource timeout can help slow resources but does not indicate that JavaScript is complete.
  • Select sync or async based on duration: synchronous requests have a shorter documented ceiling than asynchronous requests. Async changes how a long-running document is handled and retrieved, not when the page is ready.
  • Cost: no price or per-document cost should be inferred from the request limits. Check DocRaptor’s current pricing and your account terms before estimating production spend.

8. Or skip the browser setup

If your goal is a clean screenshot of a URL rather than a PDF with DocRaptor-specific layout, ScreenshotNeo offers a one-call website screenshot API and MCP server. Its PDF option is also available. The screenshot request below captures a page as WebP; see the ScreenshotNeo API documentation for output formats and options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for ScreenshotNeo’s free plan.

FAQ

Does the hook need to be called by my application?

No. Define docraptorJavaScriptFinished() in the page. DocRaptor calls it to check whether the page has finished the work needed for conversion.

Can I use a fixed sleep for every page?

You can use a fixed delay, but it depends on the delay fitting the page’s actual load behavior. A condition based on the required content is usually more reliable.

Does an asynchronous DocRaptor request wait for JavaScript automatically?

Async changes request handling and the available duration. It does not replace the page-side readiness hook for delayed JavaScript content.

Should I enable the standard and Prince engines together?

Usually no. They are separate engines, and enabling both can run code twice. Choose based on page compatibility and whether you need Prince-specific PDF scripting.

Can I expose the API key in a browser app?

Do not put a live key in a publicly accessible page. Send the request from a server-side component that keeps the credential private.