ReflowPDF API api.reflowpdf.com/v1

    JavaScript

    A document that builds itself with scripts prints what its scripts made.

    With options.javascript: true, the document's scripts run before it prints, over the engine's own document (the one it prints), with its own layout answering what they measure. What they could not do is said in the response.

    curl https://api.reflowpdf.com/v1/pdf \
      -H "Authorization: Bearer $REFLOWPDF_API_KEY" \
      -H "Content-Type: application/json" \
      -d @report.json -o report.pdf   # {"html": "…<script>…</script>…", "options": {"javascript": true}}

    What runs#

    • Classic scripts, in the document's order (HTML §4.12.1): inline, and external by an https: or data: address, fetched as the document's images are (public addresses, up to 5 MB, 20 s; none with allow_remote: false), up to 50 a document. A relative src resolves from the document's <base href>: options.base_url, or the page's own address when it is printed by url.
    • Scripts the scripts insert run too, as in a browser: a created <script> appended to the document (inline at once, external when fetched), and what document.write writes while a script runs. Markup set by innerHTML does not run its scripts, as in a browser.
    • Then DOMContentLoaded and load, promise jobs, timers, animation frames, resize observers and idle callbacks — until nothing is left to do; then beforeprint, and the queues again. Time is a clock that starts at the moment of the print: new Date() is the print's date, and a timer does not wait for real time.
    • Not run: modules (type="module", named in scripts.modules) and data blocks (application/json, application/ld+json, text/template — scripts read them, as they should).

    The page the scripts see#

    • The window is the page area of the first sheet: innerWidth is its width in CSS pixels (A4 with 20 mm margins: 643), matchMedia('print') matches and matchMedia('screen') does not: the same media queries as the document's style sheets. A chart that sizes itself to its container is drawn at the width it prints at.
    • Layout is the engine's own: getBoundingClientRect, offsetWidth, getComputedStyle answer from the layout that prints, with the same fonts, external style sheets and images. Heights are of one continuous page — sheets are cut after the scripts.
    • navigator.language is the document's lang (en-US without one); navigator.onLine says whether the print may fetch; devicePixelRatio is 1 — a print's canvas is vector (below), so no chart needs more pixels.

    What scripts can use#

    The documentThe DOM: elements with their own interfaces (HTMLFormElement, SVGTextElement, …), attributes and attributes, text and comments, innerHTML, selectors, events, <template>, SVG and MathML elements (createElementNS), TreeWalker, MutationObserver, DOMParser, XMLSerializer, document.implementation
    Its stylesgetComputedStyle, style sheets and their rules (CSSOM), CSS.supports, @font-face and FontFace by script, matchMedia
    Its layoutgetBoundingClientRect, offsetWidth and the rest, elementsFromPoint, ResizeObserver; in SVG, getBBox, getComputedTextLength and getNumberOfChars, measured with the faces the text prints in, HTML inside <foreignObject> laid out as it prints
    CanvasgetContext('2d'): paths, arcs, curves, roundRect, fills and strokes with dashes, linear and radial gradients, patterns, clipping, shadows, transforms, Path2D, drawImage, putImageData, text with measureText, printed as vector graphics, the text as outlines of the same faces it is measured with
    The windowsetTimeout, requestAnimationFrame, requestIdleCallback, Promise, fetch and XMLHttpRequest (GET, below), URL, URLSearchParams, TextEncoder/TextDecoder, structuredClone, Blob, FileReader, FormData, URL.createObjectURL (a blob: image prints), crypto.getRandomValues, localStorage (empty, for this print), history.pushState, MessageChannel, AbortController, console (kept: scripts.console)
    LanguagesIntl with the Unicode CLDR data: NumberFormat (currencies, percentages, compact and scientific notation, units of length, mass, time, area and volume), DateTimeFormat with IANA time zones, PluralRules, Collator and localeCompare, ListFormat, RelativeTimeFormat, DisplayNames, Segmenter, Locale; toLocaleString of numbers and dates

    Languages and dates#

    • The default locale is the document's lang (en-US without one) and the default time zone is UTC: a document that formats with no locale is formatted in its own language, the same on every print.
    • Formats come from the Unicode CLDR through ICU4X. They are the CLDR's, so a few differ from a browser's ICU by a detail: a numeric date in some languages keeps its zero padding (02.11.2026 in German, 2026/11/02 in Japanese).
    • A unit the data has no names for (bytes, temperatures, speeds, angles) is a NotSupportedError naming it, not a bare number.

    Fetching data#

    A script may fetch what the document itself could: fetch() and XMLHttpRequest read data: addresses and, unless allow_remote is false, public https addresses, anonymously, with the same guard as the document's images: up to 50 a print, 5 MB each, 25 s in all. Only GET and HEAD: a print sends nothing. A relative address resolves from the document's <base href>. A response that is not a 2xx is not delivered: for the script it is a network error, as is an address that cannot be reached.

    Libraries#

    Checked by printing their own examples:

    PrintChart.js · D3 · Highcharts · ECharts (SVG and canvas renderers) · Plotly · ApexCharts · Mermaid (HTML and SVG labels) · JsBarcode · qrcode-generator · KaTeX · highlight.js · marked · Handlebars · Day.js · Vue · Alpine.js
    • What a print has no use for is not there: workers, WebGL, WebSocket, IndexedDB, media playback; dialogs answer as with no one to show them to (alert returns, confirm is false, prompt is null).
    • Reading a canvas's pixels (getImageData, toDataURL) is not available: the canvas is vector, it has no pixels; a script that tries gets a NotSupportedError naming it. So does any other ability the engine lacks: it is named in scripts.errors, never answered with a guess.
    • Limits: about 15 seconds of tight work (400 000 interpreter steps), 30 seconds in all, 256 MB. Past one, the scripts stop where they are (scripts.stopped says which) and the document prints as they left it.
    • The engine in your own pages (@reflowpdf/engine) does not run scripts: run them in the page, then print its HTML.

    The response#

    In the JSON response, scripts; beside the file, ReflowPDF-Scripts, the same object as percent-encoded JSON:

    {
      "run": 4,
      "errors": [
        "script #6: notDefined is not defined"
      ],
      "error_count": 1,
      "skipped": [],
      "modules": [],
      "stopped": null,
      "console": [
        "warn: using fallback data"
      ]
    }

    An error does not stop the print: each script runs, as in a browser, and the document prints as the scripts left it.