ScreenshotNeo

BlogHTML to image & PDF

How to Handle jQuery AJAX Requests Before Generating PDFs with wkhtmltopdf

Wait for required jQuery AJAX data to render before wkhtmltopdf prints the page. Use a readiness signal for reliable PDFs, with runnable examples and fixes.

By the ScreenshotNeo team30 September 20269 min read

How to Handle jQuery AJAX Requests Before Generating PDFs with wkhtmltopdf

To include data loaded by jQuery AJAX in a wkhtmltopdf PDF, start conversion only after every required request has succeeded and its response has been rendered into the DOM. Signal that point with window.status, then run wkhtmltopdf with --window-status. A fixed JavaScript delay can be a fallback, but it cannot tell whether a slow request has finished or a request has failed.

$.ajax() is asynchronous by default: execution continues while the request is in flight. Keep its jqXHR, handle success and failure, and make the PDF renderer wait for an explicit ready state. This guide shows the single-request and multi-request patterns, renderer commands, diagnostics, and a managed screenshot alternative.

1. Why the PDF misses AJAX data

The page and the PDF renderer have separate timing. Calling $.ajax() starts a request and returns a jqXHR object; it does not pause the JavaScript that follows. If the next line asks wkhtmltopdf to print, the HTML may still contain a loading placeholder when the renderer captures it.

jQuery documents that AJAX is asynchronous by default and that response handling belongs in callbacks. The returned jqXHR provides .done(), .fail(), and .always() methods. Use success handling to update the page and only then mark it ready. See the official jQuery.ajax API and the jQuery Learning Center’s AJAX guide.

The reliable sequence is:

  1. Start the data request or requests.
  2. Validate the response and render the required content into the DOM.
  3. Set a readiness signal after rendering has completed.
  4. Have wkhtmltopdf wait for that signal before printing.

2. Handle one required request

This example assumes the page already loads jQuery and contains an element with id report. The endpoint returns JSON with a title and an array of rows. Adjust the response fields to match your API. A failure sets a separate status so the caller can avoid silently producing an incomplete report.

Wait for every required response and finish rendering before signaling the PDF renderer.
Wait for every required response and finish rendering before signaling the PDF renderer.
$(function () {
  window.status = "pdf-loading";

  $.ajax({
    url: "/report-data",
    method: "GET",
    dataType: "json",
    timeout: 20000
  })
    .done(function (data) {
      $("#report").empty();
      $("<h1>").text(data.title).appendTo("#report");

      var $list = $("<ul>").appendTo("#report");
      data.rows.forEach(function (row) {
        $("<li>").text(row.label + ": " + row.value).appendTo($list);
      });

      // Set this only after all required DOM work is complete.
      window.status = "pdf-ready";
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
      console.error("Report data failed", {
        status: jqXHR.status,
        textStatus: textStatus,
        error: errorThrown,
        response: jqXHR.responseText
      });
      window.status = "pdf-error";
    });
});

Use DOM text APIs such as .text() for untrusted values rather than concatenating them into HTML. That avoids interpreting report data as markup. If rendering itself is asynchronous—for example, a chart library returns a promise or image assets must finish loading—wait for that work too and set pdf-ready only when the final printable state is present.

Do not put the ready assignment in .always(). That callback runs after both success and failure, so it can label an error page as complete. Use it for cleanup, such as hiding a spinner. Keep the explicit success and failure paths separate.

3. Coordinate several requests

If a report needs customer details and invoice lines, retain both jqXHR objects and aggregate those dependencies with $.when(). Its success callback runs after all supplied requests succeed. jQuery’s when API documents using jqXHR objects with this Deferred interface.

$(function () {
  window.status = "pdf-loading";

  var customerRequest = $.ajax({
    url: "/customer/42",
    dataType: "json",
    timeout: 20000
  });
  var linesRequest = $.ajax({
    url: "/invoice/9001/lines",
    dataType: "json",
    timeout: 20000
  });

  $.when(customerRequest, linesRequest)
    .done(function (customerResult, linesResult) {
      // Each jqXHR result is [data, textStatus, jqXHR].
      var customer = customerResult[0];
      var lines = linesResult[0];

      renderCustomer(customer);
      renderLines(lines);
      window.status = "pdf-ready";
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
      console.error("A required report request failed", {
        status: jqXHR && jqXHR.status,
        textStatus: textStatus,
        error: errorThrown
      });
      window.status = "pdf-error";
    });
});

Define the dependency set narrowly. Optional recommendations, telemetry, and unrelated widgets should not prevent a required report from becoming ready. Conversely, if the report needs an optional-looking request for a total or footer, include it in the aggregate.

When requests are created dynamically, collect their jqXHRs in an array and pass the array to $.when.apply($, requests). Handle the empty-array case explicitly: if there is no required data request, render the static content and set ready directly. Do not confuse an HTTP success with valid report content; check required fields and decide whether malformed data is a hard failure.

4. Make wkhtmltopdf wait for readiness

Run the renderer against the report URL with the same status string used by the page:

wkhtmltopdf --window-status pdf-ready \
  https://example.test/report \
  report.pdf

The wkhtmltopdf command usage documents --window-status, --run-script, JavaScript controls, and load-error handling. Confirm that your installed build and any wrapper expose the option. A page that never reaches the requested status will not satisfy the handshake, so make sure the failure path is visible in logs and impose a timeout in the service that launches conversion.

JavaScript must remain enabled; do not pass --disable-javascript. --run-script can run additional JavaScript after page load, but it does not replace application-level knowledge of whether the required AJAX responses were rendered. For library integrations, the corresponding load.jsdelay setting waits after page load until the delay expires or JavaScript calls window.print(); see the library settings reference.

Delay fallback

If a wrapper cannot pass a status option, a measured delay is a pragmatic fallback:

wkhtmltopdf --javascript-delay 1500 \
  https://example.test/report \
  report.pdf

Choose the delay from observed request and render times in the actual deployment environment, with headroom for normal variation. A delay is a timer, not a success check: it can waste time on fast pages and still print too early on slow ones. It also cannot distinguish a failed request from a successful, empty report.

5. When to use ajaxStop

jQuery’s global ajaxStop event fires when all global AJAX requests have completed. It can be appropriate when every global request on the page belongs to the document being printed:

$(document).on("ajaxStop", function () {
  renderAnyPendingReportState();
  window.status = "pdf-ready";
});

Use this carefully. Analytics, polling, chat, or other widgets may issue requests unrelated to the report. Those requests can delay readiness, and a later unrelated request may complicate the lifecycle. Also, ajaxStop means requests have stopped; it does not itself prove required requests succeeded or that rendering has completed. For most reports, explicitly aggregating the required jqXHR objects gives better scope and failure visibility.

6. Request and renderer configuration

Setting or condition What to check
JavaScript Leave JavaScript enabled in wkhtmltopdf. The page-side AJAX and readiness code depends on it.
AJAX URL access The renderer host needs DNS, network, and TLS access to each endpoint. A URL reachable from your laptop may not be reachable from a container or server.
Origin and CORS Browser AJAX follows same-origin rules. For cross-origin endpoints, configure server-approved CORS or use a same-origin backend route. See jQuery’s cross-domain and request documentation.
Authentication Supply the session cookie or authorization context the report requires. A normal browser session may have credentials unavailable to the renderer. Avoid putting secrets in a public URL or client-side script.
Proxy and TLS Check the renderer process’s proxy, certificate trust, and outbound network policy. Diagnose from the same host and runtime that creates PDFs.
Local HTML assets For local-file input, review wkhtmltopdf’s local-file access policy and explicitly allow only the needed paths when required. Do not broadly enable local access without considering what files the renderer can read.
Resource failures wkhtmltopdf offers load-error modes such as abort, ignore, and skip. Select behavior based on whether a partial document is acceptable.

Keep the AJAX timeout, renderer wait, and outer job timeout aligned. If the request can take 20 seconds but the PDF worker gives the page only a shorter window, the worker will fail before the page can report the outcome. Set a bounded timeout on requests and jobs, log each phase, and return a clear error to the caller when required data is missing.

7. Troubleshooting

Symptom Likely cause Fix
PDF contains “Loading…” or an empty table Conversion starts immediately after $.ajax(), or ready is set before DOM rendering. Move readiness into the success path after rendering, and use --window-status pdf-ready.
PDF job waits until its timeout The status string does not match, JavaScript is disabled, the request failed, or a code path never sets a terminal state. Use identical status spelling, inspect JavaScript logs, add explicit pdf-error handling, and keep a worker-level timeout.
Browser works but server PDF does not Different network access, DNS, TLS trust, proxy, credentials, or CORS behavior. Test the endpoint from the renderer host and provide the required session or authorization context securely.
Only some report sections appear Not every required request was included in the aggregate, or a nested render step remains asynchronous. List report dependencies, await each required request and rendering step, then set ready once at the end.
Ready is set despite missing data Readiness was assigned in .always() or ajaxStop was treated as success. Set ready only on validated success; set an error state for required failures and reject or explicitly mark partial output.
A long fixed delay still misses data Request or rendering time exceeded the guessed delay. Use a status handshake. If constrained to a delay, measure under realistic load and set an explicit failure policy.
Images or local styles disappear Assets are not reachable, local access is restricted, or resources fail while the page itself loads. Check asset URLs and local access settings; inspect renderer output and choose load-error handling deliberately.
Page becomes unresponsive with async: false Synchronous AJAX blocks the browser. Remove async: false and use jqXHR callbacks or $.when(). jQuery strongly discourages synchronous requests.

For diagnosis, first open the report URL in a browser and verify the final DOM and data. Then inspect JavaScript errors and the request’s status, response, and timing. Repeat from the renderer environment, because it may have different access and credentials. Keep wkhtmltopdf’s debug output available during investigation, and avoid treating a partially rendered PDF as a successful report.

8. Performance, reliability, and cost

Parallelize independent requests and wait only for those the PDF requires. Avoid serial request chains unless later requests depend on earlier responses. Render after data arrives, and keep polling intervals or unrelated widgets out of the readiness protocol. An explicit signal reduces unnecessary fixed waiting while preserving a clear success boundary.

Reliability comes from bounded operations and explicit outcomes: request timeout, renderer/job timeout, structured error logging, and a decision about whether missing data should fail the PDF or produce a clearly identified partial document. Reuse stable report data where the application permits it, but ensure authorization and freshness requirements are respected. wkhtmltopdf conversion cost depends on your own worker resources and document workload; this topic’s sources provide no benchmark or universal per-PDF cost.

9. Or skip the browser setup

If you need a screenshot or PDF capture endpoint rather than managing a browser renderer, ScreenshotNeo is a website screenshot API and MCP server. One GET request accepts a URL and returns an image or PDF. Its capture options include wait-for-selector, delay, and network-idle controls, which can help with pages that render after loading. See the ScreenshotNeo API documentation for request options.

A clean capture removes common overlays before producing the page image or PDF.
A clean capture removes common overlays before producing the page image or PDF.
curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.test/report \
  -o report.webp

Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free account for 1,000 screenshots a month, with no card required.

10. FAQ

Does setting a longer delay guarantee complete report data?

No. It only waits a fixed amount of time. A readiness signal tied to successful requests and completed rendering provides a meaningful completion condition.

Can I generate the PDF from inside the AJAX callback?

Yes, if that callback also controls when your PDF job is submitted. When wkhtmltopdf itself loads the page, use a page-to-renderer handshake such as window.status.

Should a report still print if one request fails?

That depends on the report’s contract. For required data, fail clearly. For optional sections, define an explicit partial-output policy and label omissions so a partial result is not mistaken for a complete report.

Does ajaxStop wait for non-jQuery fetch calls?

No. It tracks jQuery’s global AJAX activity. If the page uses other request mechanisms, include their completion in your own readiness logic.

References