Returning data from your JavaScript

Send values from your own JavaScript back with a render using window.customUrlboxData, and read them from the JSON response, the async status response, webhooks or the metadata file.

The js option runs your own JavaScript in the page before Urlbox captures it. That JavaScript can also hand data back to you: assign a value to window.customUrlboxData and Urlbox returns it with the render, under metadata.custom.

It's useful whenever the screenshot alone doesn't tell the whole story: a price or stock level you read from the page, a check your script ran (did the product grid load? was a login wall shown?), or anything else you want to store next to the capture.

js requires the Ultra plan or above.

A minimal example

Set save_metadata to true and assign your data in js:

Request
{  "url": "https://example.com",  "save_metadata": true,  "js": "window.customUrlboxData = { status: \"rejected\", reason: null, checks: {}, items: [1, null, 3] };"}

When the render finishes, your data comes back exactly as you set it, at metadata.custom:

Response
{  "renderUrl": "https://renders.urlbox.com/ub-temp-renders/renders/.../<render-id>.png",  "size": 71850,  "metadataUrl": "https://renders.urlbox.com/ub-temp-renders/renders/.../<render-id>.json",  "metadata": {    "title": "Example Domain",    "urlRequested": "https://example.com",    "urlResolved": "https://example.com/",    "custom": {      "status": "rejected",      "reason": null,      "checks": {},      "items": [1, null, 3]    }  }}

Urlbox never interprets the value. A {"status": "rejected"} from your script is just data: the render itself still succeeds, and deciding what "rejected" means is up to you.

Where to read it

The same metadata object, including custom, is returned everywhere a render's result is:

  • Synchronous requests return it in the JSON response, as above. For render links, add response_type=json.
  • Asynchronous requests return it in the status response once status is succeeded, so poll the statusUrl as usual and read metadata.custom. See Sync vs Async.
  • Webhooks carry it in the render.succeeded payload under result.metadata. See Webhooks.
  • The metadata file at metadataUrl holds the same metadata object on its own.

All four contain the same values, including nulls, empty objects and arrays, and keys with dots in them. Read whichever suits your integration, with two things to allow for:

  • The inline metadata is capped at 1 MB. If the whole object (title, tags, links, structured data and custom together) would be larger, its largest fields are left out of the inline copy and listed in metadata.omittedFields. The file at metadataUrl always has everything, so fetch it when omittedFields is present.
  • Saving the file is best-effort. If it ever fails, the render still succeeds and metadataUrl is simply absent, so don't rely on the file alone.

How the value is read

Urlbox reads window.customUrlboxData once, after your JavaScript has run and the page has been captured, then converts it with JSON.stringify. That gives a few rules worth knowing:

  • Objects, arrays, strings, numbers and true come back exactly as you set them.
  • Unset, null, false, 0 and "" leave the custom key out of metadata entirely. If you need to tell "my script ran and found nothing" apart from "my script never ran", always assign an object, for example { found: false }.
  • Normal JSON conversion applies: functions and undefined values are dropped, NaN and Infinity become null, Date objects become ISO strings, and a Map, Set or Promise comes back as {}.
  • Values that can't be converted, such as a circular reference or a BigInt, return "custom": {"error": "<message>"} instead. If you use an error field of your own, keep it inside a nested object so you can tell the two apart.
  • Values assigned later, for example inside a setTimeout or after a network request, are missed if they arrive after the capture. Your script runs as an async function and Urlbox waits for it to finish (for up to 30 seconds), so await what you need before assigning:
const res = await fetch("/api/stock-level");
window.customUrlboxData = { stock: (await res.json()).level };
  • Values over 1 MB (about a million characters of JSON) aren't returned. custom is {"error": "customUrlboxData is too large: …"} instead, and the render still succeeds. Return a summary rather than, say, the page's whole HTML: there's save_html for that.

Using a different variable name

If window.customUrlboxData clashes with something on the page, set custom_data_variable to the name of another global, and assign your data to that instead:

Request
{  "url": "https://example.com",  "save_metadata": true,  "custom_data_variable": "myScanResult",  "js": "window.myScanResult = { headline: document.querySelector(\"h1\")?.textContent ?? null };"}

The metadata file

The file at metadataUrl is written by the same render, from the same page load, as the screenshot:

  • Its path matches renderUrl, with .json in place of the image extension, and its file name is the render's id. To check a file belongs to the render you polled, confirm it's served over HTTPS from renders.urlbox.com (or your own storage bucket or CDN host, if you use one) and that the path ends in <render-id>.json.
  • The URL has no signature or expiry parameter. It's a public file at a path that can't be guessed.
  • Files in Urlbox's default storage are deleted after about 30 days. Copy anything you need to keep, or save renders to your own bucket.
  • If the data is sensitive, use secure_mode with your own private bucket. The metadata file is written only to your bucket, and Urlbox keeps no copy of the render's content, including custom, once it has been delivered.
  • Straight after a render finishes, a fetch of the file can very occasionally fail for a moment. Retry it once before treating it as missing.
  • Side Renders covers the rest of save_metadata (title, open graph and meta tags) and the other artifacts you can save from the same page load.
  • Webhooks explains the render.succeeded payload and how to verify the X-Urlbox-Signature header.