# HTML to PDF in Bubble

Convert HTML to PDF in Bubble with the API Connector. Set up the call, pass dynamic HTML, get a PDF link back and save it to a file field.

Bubble apps often need to hand users a document, such as an invoice, a quote, a certificate or a report. Bubble has no built-in HTML-to-PDF action, so most apps either use a plugin or call a PDF API through the API Connector.

This guide covers the API Connector route. It gives you full control over the layout with your own HTML and CSS.

**Quick answer:** in the API Connector, add a `POST` call to `https://html-pdf.com/v1/pdf` with the header `Authorization: Bearer YOUR_API_KEY` and this JSON body. The response contains a `url` to the PDF.

```json
{ "html": <html>, "response": "url" }
```

## Native options and their limits

This section covers what you can do without an API, and why it falls short.

- **Browser print.** You can open a page and let the user "Print → Save as PDF". The result depends on the user's browser, includes page chrome, and can't run in a backend workflow.
- **PDF plugins.** Several marketplace plugins convert a page or a group to PDF in the browser. Many rasterize the screen into an image, so text isn't selectable and long content splits awkwardly.

A server-side Chromium render avoids both problems. You get selectable text, real page breaks, headers and footers, and it works in backend workflows.

## Step 1: get an API key

First, create a free account.

Sign up at https://html-pdf.com/login. You get 100 free credits (1 credit = 1 PDF page), and purchased credits never expire.

## Step 2: add the API in the API Connector

Next, register html-pdf.com as an API with your key.

Install the **API Connector** plugin (by Bubble), then click **Add another API** and fill in these values.

- **API Name:** `html-pdf`
- **Authentication:** `Private key in header`
- **Key name:** `Authorization`
- **Key value:** `Bearer YOUR_API_KEY`

A private key in header stays on Bubble's server and is never exposed to the browser.

## Step 3: configure the call

Now add the call that renders the PDF. Name it `Render PDF` and use these settings.

- **Use as:** `Action` (so it appears in workflows)
- **Data type:** `JSON`
- **Method / URL:** `POST` `https://html-pdf.com/v1/pdf`
- **Headers:** `Content-Type` = `application/json`
- **Body type:** `JSON`

Use this body.

```json
{
  "html": <html>,
  "format": "A4",
  "margin": "15mm",
  "print_background": true,
  "filename": "document.pdf",
  "response": "url"
}
```

`<html>` creates a dynamic parameter. Uncheck **Private** next to it so you can set it from workflows.

Bubble inserts the value as-is, so it must be a valid JSON string including the quotes. When you set it in a workflow, use the dynamic expression with `:formatted as JSON-safe`, which adds the quotes and escapes characters like `"` and line breaks.

### Why `"response": "url"`

By default the API returns raw PDF bytes. Bubble's API Connector expects a JSON (or text) response and won't turn binary data into a file. With `"response": "url"` the API returns JSON instead.

```json
{ "url": "https://...", "expires_at": "..." }
```

The link is valid for 24 hours, which is plenty for Bubble to download it into its own storage.

## Step 4: initialize the call

Bubble needs one test request to learn the response shape.

Give the `html` parameter a test value such as `"<h1>Hello from Bubble</h1>"` (with the quotes, since it's raw JSON here) and click **Initialize call**. Bubble sends a real request (it uses 1 credit) and shows the detected fields. Confirm that `url` is a text field and save.

If initialization fails, the response body explains why. Errors look like `{"error": {"code", "message", "hint"}}`, for example `401` for a wrong key or `402 insufficient_credits` when your balance is empty.

## Step 5: use it in a workflow

Here is a typical "Download invoice" button workflow.

1. **Plugins → html-pdf - Render PDF**, with `html` set to your HTML. Build it with Bubble's text editor and dynamic data, for example `<h1>Invoice Current Page Invoice's Number</h1>...`, then apply `:formatted as JSON-safe` to the whole expression.
2. **Make changes to a thing** on the invoice. Set its `PDF` (file field) to `Result of step 1's url`. Bubble saves the file from the URL into its own storage so it outlives the 24-hour link. If your editor doesn't accept a URL for a file field directly, store the URL in a text field and save the file with a file-upload plugin, or save it in a backend workflow.
3. Optional: **Open an external website** with `Result of step 1's url` to let the user download it immediately, or **Send email** with the link.

The same call works in **backend workflows** (API workflows). You can generate PDFs on a schedule or when data changes, without a user on the page.

## Building the HTML

These tips help your template print cleanly.

Keep your template in an option set, a text field, or a "template" thing, and fill in dynamic values with Bubble expressions.

- Use inline `<style>` with `@page { size: A4; margin: 18mm; }` and send `"prefer_css_page_size": true`.
- Add `break-inside: avoid` on table rows and totals so they don't split across pages.
- Reference images and fonts by absolute `https://` URLs.
- Escape user-entered text before inserting it into HTML.

Ready-made layouts are at https://html-pdf.com/templates, and a full invoice example is at https://html-pdf.com/generate-invoice-pdf.

## Rendering a page by URL

You can also turn an existing page into a PDF instead of sending HTML.

Send `"url": "https://..."` in place of `html`. The page must be publicly reachable without login, so this is best for public pages. For private data, pass HTML.

Use `wait_delay` or `wait_for_expression` if the page draws content with JavaScript after loading.

## More options

These extra options cover most layout needs.

- `format`: A3 to A6, Letter, Legal, Tabloid. Use `landscape: true` for wide tables.
- `header_html` / `footer_html` with `pageNumber` and `totalPages` for "Page 1 of 3".
- `max_pages` to cap cost on unexpectedly long documents.

Requests can be up to 20 MB with 30 seconds per render.

## FAQ

### How do I create a PDF in Bubble?

Add a `POST` call to `https://html-pdf.com/v1/pdf` in the API Connector with your HTML and `"response": "url"`. Run it as a workflow action and save the returned `url` to a file field.

### Why does the API Connector not return a file?

Bubble's API Connector can't turn binary PDF bytes into a file. Send `"response": "url"` to get JSON with a download link valid for 24 hours, and let Bubble save it from there.

### Does it support page numbers?

Yes. Use `header_html` or `footer_html` with the `pageNumber` and `totalPages` classes to print "Page 1 of 3".

### Is there a free plan?

You get 100 free credits once you confirm your email, with no card. One credit is one PDF page, and purchased credits never expire.

Full reference: https://html-pdf.com/docs.
