Skip to content
CastPDF

A PDF generator for Retool, built on a REST API resource

To generate PDFs in Retool, create a REST API resource for https://api.castpdf.com/v1 with an Authorization header, then add a query that POSTs to /pdf with data from your components. Ask for "response": "url" and open the link from a button. Our PDF generation API does the rendering.

  • Updated October 2026
  • 100 free PDFs a month

What you will build, and what you will not need

The goal is a button in a Retool app that turns the selected record into a PDF and opens it in a new tab. Support teams use this for statements, operations teams for job sheets, finance for credit notes. It takes one resource, one query and one event handler.

To be clear from the start: CastPDF has no Retool integration, resource type or listing of its own, and a ready-made CastPDF app for Retool is not available yet. You connect through the generic REST API resource that every Retool organisation already has. Nothing needs installing.

Screens change

Retool moves and renames settings from release to release. The labels here match the editor when this guide was written. If a field has moved, look for the one that does the same job: base URL, header, method, body, timeout.

Create the resource and the query

Use a CastPDF test key while you build. It is free, puts a watermark on each page and allows 20 requests per minute. You also need a template in the CastPDF dashboard; its ID is on the template page.

  1. Create a REST API resource. Go to Resources, choose Create new, then REST API. Name it "CastPDF".
  2. Set the base URL. Enter https://api.castpdf.com/v1 as the Base URL. Each query adds its own path.
  3. Add the Authorization header. Under Headers, add Authorization with the value Bearer followed by your key. Add Content-Type with application/json too. Save the resource.
  4. Add a query in your app. Open the app, add a new resource query and pick the CastPDF resource. Name it generatePdf.
  5. Set method, path and body. Choose POST and type /pdf after the base URL. Set the body to Raw and paste the expression from the next section.
  6. Run it only on demand. Make sure the query runs only when triggered, never on page load or input change. Otherwise every visit to the app creates a PDF.
  7. Raise the timeout. In the query’s advanced settings, set the timeout to at least 60000 ms. A render may take up to 30 seconds, and the default is shorter.

Why a resource instead of a header typed into each query? Retool stores resource settings on its server, and resource queries run through its backend. The key never reaches the browser of the person using the app. It also lives in one place, so moving from the test key to a live key is one edit.

If your Retool plan has separate environments, give the resource the test key in staging and the live key in production. Builders then test freely without using up paid PDFs.

The request body, built from your components

Picture a field service company. Its Retool app lists finished jobs in table1, and a second query, partsQuery, loads the parts used on the selected job. The team wants a job sheet PDF for the customer. This raw body builds the whole request in one expression:

generatePdf: raw body
{{ JSON.stringify({
  template_id: '5b2e8f41-9c3d-4a7e-b610-2d4f8a9c1e37',
  data: {
    job: table1.selectedRow,
    parts: formatDataAsArray(partsQuery.data),
    signed_off_by: current_user.fullName
  },
  filename: 'job-' + table1.selectedRow.job_number + '.pdf',
  response: 'url'
}) }}

Building the body with JSON.stringify saves you from quoting by hand. A customer note with a quote mark or a line break would otherwise break the JSON. The formatDataAsArray helper matters for SQL queries, which Retool returns column by column. CastPDF templates loop over rows, so the parts need to be an array of objects.

One detail depends on your table component. In the current Table, table1.selectedRow is the row object itself. Older apps with the legacy table nest it under table1.selectedRow.data.

Here is what CastPDF receives for one job once Retool fills in the values. Only real request fields are allowed, and an unknown one is rejected with a 400 error.

What CastPDF receives
{
  "template_id": "5b2e8f41-9c3d-4a7e-b610-2d4f8a9c1e37",
  "data": {
    "job": {
      "job_number": "J-88213",
      "customer": "Ridgeview Dental Clinic",
      "technician": "Tomasz Wilk",
      "completed_at": "2026-10-02",
      "notes": "Replaced the compressor filter and tested the pressure."
    },
    "parts": [
      { "sku": "CF-220", "name": "Compressor filter", "qty": 2, "unit_price": 18.5 },
      { "sku": "HS-14", "name": "Hose clamp", "qty": 4, "unit_price": 1.2 }
    ],
    "signed_off_by": "Dana Whitfield"
  },
  "filename": "job-J-88213.pdf",
  "response": "url"
}

Open the PDF from a button

With "response": "url", the query returns JSON with url, pages, bytes, id and expires_at. The url is a signed link that works without an API key, so it is safe to open in the browser. There are two easy ways to wire it up.

Event handlers, no code

Add a Button labelled "Job sheet PDF". Give it a Click event handler that triggers generatePdf. On the query, add a Success event handler with the action that opens a URL, and set the URL to {{ generatePdf.data.url }} with the new tab option on.

One JavaScript query

If you prefer code, point the button at a JavaScript query with these two lines. It waits for the PDF, then opens the link with utils.openUrl.

openJobSheet (JavaScript query)
const result = await generatePdf.trigger();
utils.openUrl(result.url, { newTab: true });

Set the button to show a loading state while generatePdf.isFetching is true. A render takes a few seconds, and the spinner stops people from clicking twice. You can also turn on the query option that asks for confirmation before it runs.

The link expires at expires_at, which follows your plan’s storage period. Test key files last 1 day. If the PDF should stay attached to the job, save the returned id or url to your database in the same success handler, and fetch a fresh link later with GET /v1/pdf/:id.

When the query fails

CastPDF errors are JSON with a code, a message and a docs_url. Retool shows them in the query’s response panel and marks the run as failed, so a Failure event handler can show a notification with generatePdf.error.

  • 401 invalid_api_key. The header on the resource is wrong. It must be named Authorization, and its value must be Bearer, a space and the key, with nothing else.
  • 422 template_render_error. The template could not render with this data, and details point to a line and column. Often selectedRow is empty because no row was selected, so disable the button until one is.
  • 400 invalid_request. The body is not valid JSON or has a field CastPDF rejects. Build it with JSON.stringify, and check that template_id is a full ID.
  • 429 rate_limited. Too many requests in a minute, or too many renders at the same moment. Wait the seconds in the Retry-After header before running the query again.
  • Timeouts. A render may run for 30 seconds. If the query gives up first, raise its timeout to 60000 ms or more.

For double clicks and retries, add an Idempotency-Key header to the query with a value such as {{ 'job-' + table1.selectedRow.job_number }}. A repeat with the same key returns the first PDF and is not billed again. If the job data changes, add a version to the key, because the same key with a different body is rejected. The errors reference lists every code.

What it costs

CastPDF bills one PDF per request, whatever the page count, and test keys are free for building. The Free plan includes 100 live PDFs a month; the paid plans are on the pricing page. Retool’s own plan costs apply as usual, so check your plan for limits that affect query runs.

Because the query runs only on a click, you pay for the PDFs people ask for, not for page loads. That is the main reason to keep it on manual trigger.

Next steps

Design the job sheet once in HTML and CSS in the CastPDF dashboard, with a live preview, and start from the report template if you want a table that repeats its header across pages. The templates docs explain versions, and the Liquid docs cover loops and the money filter.

Generating PDFs from an automation instead of an app screen? See the guide for workflow automation nodes or the guide for visual scenario builders.

FAQ

Common questions

Does CastPDF have a native Retool resource?

No, not yet. There is no CastPDF resource type or listing in Retool today. The generic REST API resource connects to the CastPDF API with a base URL and one header.

Is my CastPDF API key exposed to Retool app users?

Not when it sits in the resource headers. Retool keeps resource settings on its server and runs resource queries through its backend. App users only see the query results.

How do I download the PDF from a Retool button?

Ask CastPDF for a url response, then open generatePdf.data.url in a new tab from the query success handler. A JavaScript query with utils.openUrl does the same in two lines. The link needs no key.

Why does my Retool PDF query time out?

The query timeout is shorter than the time a large PDF can take to render. Raise it to at least 60000 milliseconds in the query advanced settings. Send an Idempotency-Key so a retry does not create a second PDF.

How do I pass a SQL query result into the PDF template?

Wrap it in formatDataAsArray so the rows become an array of objects. Retool returns SQL results column by column, and templates loop over rows. Put the array in the data object of the request body.

Can I use a CastPDF test key in a Retool app?

Yes, and you should while you build. Test keys are free and watermark every page. Switch the resource to a live key, or use a separate production environment, when real users start clicking.

Make your first PDF in 5 minutes

Pick a template, add your details and download your PDF. You get 100 free PDFs every month, and you don’t need a card.