Get an API Key Demo

HTML to PDF Reports

Generate pixel-perfect PDF contracts, proposals, and reports from HTML — personalized per client with variable substitution.

The problem

Generating PDFs server-side usually means wrestling with PDF libraries — escaping special characters, fighting layout engines, or paying for heavy SaaS tools. If you already know HTML and CSS, you already know how to design the document. StarkRender renders it with a real browser, so what you see in the preview is exactly what lands in the PDF.


Step 1 — Design the document in HTML

Write the document as standard HTML. Use {{variable}} placeholders for anything that changes per client or per render. Print-specific CSS like @page, page-break-before, and page-break-after is fully supported.

HTML — Contract template
<!DOCTYPE html>
<html>
<head>
<style>
  @page { margin: 0; }   /* margin is controlled by the API param */
  body {
    font-family: Inter, sans-serif;
    color: #1e293b;
    line-height: 1.6;
  }
  .header {
    background: #0f172a;
    color: #fff;
    padding: 40px 60px;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .logo    { font-size: 24px; font-weight: 800; color: #C9952A; }
  .content { padding: 48px 60px; }
  h1       { font-size: 28px; margin: 0 0 8px; }
  .meta    { color: #64748b; font-size: 14px; margin: 0 0 40px; }
  table    { width: 100%; border-collapse: collapse; margin: 24px 0; }
  th       { background: #f1f5f9; text-align: left; padding: 12px 16px; font-size: 13px; }
  td       { padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-size: 14px; }
  .total   { font-weight: 700; font-size: 16px; }
  .footer  { padding: 32px 60px; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 13px; }
  .signature-block { margin-top: 60px; display: flex; gap: 80px; }
  .sig-line { border-top: 1px solid #334155; padding-top: 8px; width: 240px; font-size: 13px; color: #64748b; }
</style>
</head>
<body>

<div class="header">
  <div class="logo">YourBrand</div>
  <div style="font-size:13px;color:#94a3b8">Contract #{{contract_number}}</div>
</div>

<div class="content">
  <h1>Service Agreement</h1>
  <p class="meta">Issued: {{issue_date}} · Valid until: {{expiry_date}}</p>

  <p>This agreement is entered into between <strong>YourBrand</strong>
  and <strong>{{client_name}}</strong> (<em>{{client_company}}</em>).</p>

  <table>
    <thead>
      <tr><th>Service</th><th>Qty</th><th>Unit price</th><th>Total</th></tr>
    </thead>
    <tbody>
      <tr>
        <td>{{service_description}}</td>
        <td>{{qty}}</td>
        <td>{{unit_price}}</td>
        <td class="total">{{total_value}}</td>
      </tr>
    </tbody>
  </table>

  <div class="signature-block">
    <div>
      <div class="sig-line">YourBrand representative</div>
    </div>
    <div>
      <div class="sig-line">{{client_name}} · {{client_company}}</div>
    </div>
  </div>
</div>

<div class="footer">
  YourBrand · hello@yourbrand.com · This document is confidential.
</div>

</body>
</html>

Step 2 — Call the API

Pass the HTML with variables filled in per client. The PDF is multi-page automatically if the content overflows. Background colors and images are always preserved.

Node.js

JavaScript
const CONTRACT_HTML = `<!-- your template HTML -->`;

async function generateContract(client) {
  const res = await fetch('https://api.starkrender.com/v1/pdf', {
    method: 'POST',
    headers: {
      'x-api-key':    process.env.STARKRENDER_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html:   CONTRACT_HTML,
      format: 'A4',
      margin: '0',          // controlled by @page in the HTML
      variables: {
        contract_number:     client.contractNumber,
        issue_date:          client.issueDate,
        expiry_date:         client.expiryDate,
        client_name:         client.name,
        client_company:      client.company,
        service_description: client.service,
        qty:                 client.qty,
        unit_price:          client.unitPrice,
        total_value:         client.totalValue,
      },
    }),
  });

  const { url } = await res.json();
  return url;   // permanent PDF URL — send to client or store in DB
}

Python

Python
import os, requests

CONTRACT_HTML = """<!-- your template HTML -->"""

def generate_contract(client: dict) -> str:
    resp = requests.post(
        "https://api.starkrender.com/v1/pdf",
        headers={
            "x-api-key":    os.environ["STARKRENDER_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "html":   CONTRACT_HTML,
            "format": "A4",
            "margin": "0",
            "variables": {
                "contract_number":     client["contract_number"],
                "issue_date":          client["issue_date"],
                "expiry_date":         client["expiry_date"],
                "client_name":         client["name"],
                "client_company":      client["company"],
                "service_description": client["service"],
                "qty":                 client["qty"],
                "unit_price":          client["unit_price"],
                "total_value":         client["total_value"],
            },
        },
    )
    resp.raise_for_status()
    return resp.json()["url"]   # permanent PDF URL

Tips

Page breaks

Control pagination with standard CSS. Force a new page before a section, or prevent a table row from splitting across pages:

CSS
.new-section  { page-break-before: always; }
tr            { page-break-inside: avoid; }
.keep-together { break-inside: avoid; }

Landscape for wide tables

Pass "landscape": true for reports with many columns. Works with all format values.

JSON
{
  "html":      "<table>...</table>",
  "format":    "A4",
  "landscape": true,
  "margin":    "30px"
}

Fonts

Any Google Font referenced in your CSS is automatically detected and loaded before rendering — no setup needed. Use font-family: Inter, sans-serif in your CSS and it just works.

Store the URL, not the file

The returned URL is permanent. Store it in your database and send it directly to the client (e.g., in an email) or embed it in a download link. You don't need to stream or proxy the file through your server.

HTML — Download link
<a href="https://api.starkrender.com/v1/image/uuid" download="contract.pdf">
  Download contract
</a>

Letter and Legal formats

Pass "format": "Letter" or "format": "Legal" for US paper sizes. The default is "A4".

On This Page
The Problem Step 1 — Design the document Step 2 — Call the API Tips