# PDF generation

Actions can generate PDF files on the server and attach them to records. This is useful for invoices, reports, certificates, and document exports.

PDF rendering runs on the server. Your action code builds an HTML string, passes it to the Files API, and persists the result - all within a single server-side execution.

## Overview[​](#overview "Direct link to Overview")

The typical workflow has four steps:

1. Build an HTML string with the content you want in the PDF.
2. Call `Files.writePdfFileRef()` to render the HTML into a PDF file reference.
3. Persist the file reference with `Files.persistFileRef()`.
4. Attach the persisted file to the record.

## Complete example[​](#complete-example "Direct link to Complete example")

The action below generates a simple invoice PDF and attaches it to the current record.

`Files.writePdfFileRef()` accepts an object, not a plain string: `writePdfFileRef(input: { html?: string | null }): FileRef`, where `FileRef` is `{ ref: string; name: string }`.

```
import { entity } from "#typings";
import { Files } from "@comind/api/server";

function generate_invoice_pdf() {
  // Build the HTML content
  const html = `
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; padding: 40px; }
        h1 { color: #333; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
        th { background-color: #f5f5f5; }
        .total { font-weight: bold; font-size: 1.2em; margin-top: 20px; }
      </style>
    </head>
    <body>
      <h1>Invoice #${entity.number}</h1>
      <p>Date: ${new Date().toLocaleDateString()}</p>
      <p>Customer: ${entity.c_customer_name || "N/A"}</p>
      <table>
        <tr><th>Item</th><th>Amount</th></tr>
        <tr><td>${entity.c_item_description || "Service"}</td>
            <td>$${entity.c_total_cost || 0}</td></tr>
      </table>
      <p class="total">Total: $${entity.c_total_cost || 0}</p>
    </body>
    </html>
  `;

  // Generate the PDF from HTML
  const pdfRef = Files.writePdfFileRef({ html });

  // Save the temporary file permanently
  const storedFile = Files.persistFileRef(pdfRef.ref);

  // Attach the PDF to the record
  entity.attachments__list ??= [];
  entity.attachments__list.push({
    file_uid: storedFile.uploadedFileId,
    title: `Invoice-${entity.number}.pdf`,
  });
}

export default {
  generate_invoice_pdf,
} satisfies ActionLogic<typeof entity>;
```

## Using Liquid templates[​](#using-liquid-templates "Direct link to Using Liquid templates")

For complex documents, use `LiquidParser.parse()` to render a Liquid template before passing the result to the PDF generator.

Import `LiquidParser` from `@comind/api/server`. Signature: `parse(liquidTemplate: string, context: any): string`.

```
import { LiquidParser } from "@comind/api/server";

const template = `
  <h1>Report for {{ customer_name }}</h1>
  <p>Generated on {{ date }}</p>
  {% for item in items %}
    <p>{{ item.name }}: ${{ item.amount }}</p>
  {% endfor %}
`;

const htmlContent = LiquidParser.parse(template, {
  customer_name: entity.c_customer_name,
  date: new Date().toLocaleDateString(),
  items: JSON.parse(entity.c_line_items || "[]"),
});

const pdfRef = Files.writePdfFileRef({ html: htmlContent });
```

## Merging multiple PDFs[​](#merging-multiple-pdfs "Direct link to Merging multiple PDFs")

You can combine several PDF file references into a single document with `Files.mergePdfFileRefs()`.

`Files.mergePdfFileRefs()` takes an array of `FileRef` and returns a single merged `FileRef`.

```
const coverRef = Files.writePdfFileRef({ html: coverPageHtml });
const bodyRef = Files.writePdfFileRef({ html: bodyHtml });

const mergedRef = Files.mergePdfFileRefs([coverRef, bodyRef]);
const storedFile = Files.persistFileRef(mergedRef.ref);
```

## Creating ZIP archives[​](#creating-zip-archives "Direct link to Creating ZIP archives")

Use `Files.zipFileRefs()` to bundle multiple files - PDFs or otherwise - into a single ZIP download.

`Files.zipFileRefs()` takes an array of `FileRef` and returns a single `FileRef` for the ZIP archive.

```
const pdfRef = Files.writePdfFileRef({ html });
const zipRef = Files.zipFileRefs([pdfRef]);
const storedFile = Files.persistFileRef(zipRef.ref);
```

## Key points[​](#key-points "Direct link to Key points")

* PDF generation is **server-side only**. It runs inside [action logic](/developer-guide/building-blocks/actions.md), not in the browser.
* The `Files` global is available alongside `entity`, `ComindServer`, `Utils`, and `console`. See [Server-side API](/developer-guide/reference/server-side-globals.md) for the full list.
* HTML styling is fully supported. Use inline CSS or `<style>` blocks for layout control.
* File references returned by `writePdfFileRef()` are temporary. Always call `Files.persistFileRef()` before attaching them to a record.
* For data-driven PDFs, you can read spreadsheet or CSV content with `Files.readExcelFileRef()` and `Files.readCsvFileRef()`, then format the data into HTML.

## Related[​](#related "Direct link to Related")

* [How to download a file by URL and save as attachment](/developer-guide/how-to/advanced/how-to-download-file-by-url-and-save-as-attachment.md) - the `Files.persistFileRef()` and attachment pattern used in PDF generation
* [Server-side API](/developer-guide/reference/server-side-globals.md) - full reference for the `Files` global and other server-side objects
