Skip to main content
The PDF Member generates PDFs from HTML with support for R2 storage, browser display vs download modes, and advanced page configuration. Perfect for:
  • Invoices & Receipts: Generate professional invoices with automated delivery
  • Reports & Analytics: Multi-page reports with charts and tables
  • Certificates: Dynamic certificates with custom data
  • Documentation: Export documentation as PDF

Quick Start

Generate PDF from Inline HTML

name: simple-pdf
members:
  - name: generate-pdf
    type: PDF
    config:
      html:
        inline: |
          <!DOCTYPE html>
          <html>
          <body>
            <h1>{{title}}</h1>
            <p>{{content}}</p>
          </body>
          </html>
      deliveryMode: inline  # Display in browser

flow:
  - member: generate-pdf
    input:
      html:
        data:
          title: "My Document"
          content: "Hello PDF!"

Invoice with R2 Storage

name: invoice-pdf
members:
  - name: generate-invoice
    type: PDF
    config:
      html:
        inline: |
          <html>
          <head>
            <style>
              body { font-family: Arial; margin: 40px; }
              .header { font-size: 24px; font-weight: bold; }
              .total { font-size: 20px; margin-top: 20px; }
            </style>
          </head>
          <body>
            <div class="header">INVOICE #{{invoiceNumber}}</div>
            <p>Customer: {{customerName}}</p>
            <p>Amount Due: ${{total}}</p>
            <div class="total">Total: ${{total}}</div>
          </body>
          </html>
      storage:
        saveToR2: true
        r2Key: static/invoices/invoice-{{invoiceNumber}}.pdf
        publicUrl: true
      deliveryMode: attachment  # Force download
      filename: invoice-{{invoiceNumber}}.pdf

flow:
  - member: generate-invoice
    input:
      html:
        data:
          invoiceNumber: ${input.invoiceNumber}
          customerName: ${input.customerName}
          total: ${input.total}

HTML Sources

Inline HTML

Best for simple documents:
html:
  inline: "<h1>{{title}}</h1><p>{{content}}</p>"
  data:
    title: "Report"
    content: "Data here"

From HTML Member

Use HTML member output for complex templates:
members:
  - name: render-html
    type: HTML
    config:
      template:
        kv: templates/report@latest

  - name: convert-to-pdf
    type: PDF
    config:
      html:
        fromMember: render-html

flow:
  - member: render-html
    input:
      data: ${input.data}
  - member: convert-to-pdf

From Template

Load and render templates directly:
html:
  template:
    kv: templates/certificate@v1.0.0
  data:
    name: "John Doe"
    course: "Advanced PDF"
    date: "2025-01-09"

Page Configuration

Page Sizes

page:
  size: A4         # A4, A3, A5, Letter, Legal, Tabloid
  orientation: portrait  # portrait or landscape
Supported sizes:
  • A4: 210 � 297mm (default)
  • A3: 297 � 420mm
  • A5: 148 � 210mm
  • Letter: 8.5 � 11 inches
  • Legal: 8.5 � 14 inches
  • Tabloid: 11 � 17 inches

Custom Margins

page:
  margins:
    top: 20      # mm
    right: 20
    bottom: 20
    left: 20
  printBackground: true  # Print background colors/images
  scale: 1.0   # 0.1 - 2.0

Headers and Footers

With Page Numbers

headerFooter:
  displayHeaderFooter: true
  header: |
    <div style="font-size: 10px; text-align: center; width: 100%;">
      <span>{{companyName}} - Confidential</span>
    </div>
  footer: |
    <div style="font-size: 10px; text-align: center; width: 100%;">
      Page <span class="pageNumber"></span> of <span class="totalPages"></span>
    </div>
Variables available:
  • {{pageNumber}} - Current page number
  • {{totalPages}} - Total page count

R2 Storage

Basic Storage

storage:
  saveToR2: true
  r2Key: static/reports/report-{{id}}.pdf
Default key format: static/generated-{timestamp}-{random}.pdf

Public URLs

storage:
  saveToR2: true
  r2Key: static/invoices/{{filename}}.pdf
  publicUrl: true    # Generate public URL
  r2Binding: ASSETS  # R2 bucket binding name (default: ASSETS)
Output includes:
{
  r2Key: "static/invoices/inv-001.pdf",
  url: "/assets/static/inv-001.pdf"  // Public URL
}

Delivery Modes

Display in Browser (Inline)

deliveryMode: inline
filename: report.pdf
Sets header: Content-Disposition: inline; filename="report.pdf" User sees PDF in browser viewer.

Force Download (Attachment)

deliveryMode: attachment
filename: invoice-123.pdf
Sets header: Content-Disposition: attachment; filename="invoice-123.pdf" Browser downloads the PDF immediately.

PDF Metadata

metadata:
  title: "Q1 2025 Report"
  author: "Acme Corp"
  subject: "Quarterly Analytics"
  keywords: "analytics, report, Q1"
  creator: "Conductor PDF Generator"
  creationDate: ${input.reportDate}

Complete Examples

Professional Invoice

name: professional-invoice
members:
  - name: generate-invoice
    type: PDF
    config:
      html:
        inline: |
          <!DOCTYPE html>
          <html>
          <head>
            <style>
              /* Professional invoice styles */
              body { font-family: Arial; margin: 40px; }
              .header {
                display: flex;
                justify-content: space-between;
                margin-bottom: 40px;
              }
              .company { font-size: 24px; font-weight: bold; }
              .items-table {
                width: 100%;
                border-collapse: collapse;
                margin: 30px 0;
              }
              .items-table th {
                background: #2D1B69;
                color: white;
                padding: 12px;
              }
              .items-table td {
                padding: 12px;
                border-bottom: 1px solid #ddd;
              }
              .total {
                text-align: right;
                font-size: 20px;
                font-weight: bold;
                margin-top: 20px;
              }
            </style>
          </head>
          <body>
            <div class="header">
              <div class="company">{{companyName}}</div>
              <div>
                <strong>INVOICE #{{invoiceNumber}}</strong><br>
                Date: {{invoiceDate}}<br>
                Due: {{dueDate}}
              </div>
            </div>

            <table class="items-table">
              <tr>
                <th>Description</th>
                <th>Quantity</th>
                <th>Price</th>
                <th>Amount</th>
              </tr>
              {{#each items}}
              <tr>
                <td>{{description}}</td>
                <td>{{quantity}}</td>
                <td>${{unitPrice}}</td>
                <td>${{amount}}</td>
              </tr>
              {{/each}}
            </table>

            <div class="total">Total: ${{total}}</div>
          </body>
          </html>
      page:
        size: A4
        orientation: portrait
      storage:
        saveToR2: true
        r2Key: static/invoices/invoice-{{invoiceNumber}}.pdf
        publicUrl: true
      deliveryMode: ${input.deliveryMode}
      filename: invoice-{{invoiceNumber}}.pdf
      metadata:
        title: Invoice {{invoiceNumber}}
        author: {{companyName}}

Multi-Page Report from HTML Member

name: analytics-report
members:
  - name: render-report-html
    type: HTML
    config:
      template:
        inline: |
          <html>
          <head>
            <style>
              .cover { text-align: center; margin-top: 100px; }
              .section { page-break-before: always; }
              table { width: 100%; border-collapse: collapse; }
              th { background: #2D1B69; color: white; padding: 12px; }
              td { padding: 12px; border-bottom: 1px solid #ddd; }
            </style>
          </head>
          <body>
            <div class="cover">
              <h1>{{title}}</h1>
              <p>{{reportDate}}</p>
            </div>

            <div class="section">
              <h2>Executive Summary</h2>
              <p>{{summary}}</p>
            </div>

            <div class="section">
              <h2>Detailed Metrics</h2>
              <table>
                {{#each metrics}}
                <tr>
                  <td>{{name}}</td>
                  <td>{{value}}</td>
                </tr>
                {{/each}}
              </table>
            </div>
          </body>
          </html>

  - name: convert-to-pdf
    type: PDF
    config:
      html:
        fromMember: render-report-html
      headerFooter:
        displayHeaderFooter: true
        header: "<div style='text-align:center;font-size:10px;'>Confidential Report</div>"
        footer: "<div style='text-align:center;font-size:10px;'>Page <span class='pageNumber'></span></div>"
      storage:
        saveToR2: true
        r2Key: static/reports/{{filename}}.pdf
      deliveryMode: attachment
      filename: analytics-{{reportDate}}.pdf

flow:
  - member: render-report-html
    input:
      data: ${input}
  - member: convert-to-pdf

Configuration Reference

Member Config

type: PDF
config:
  # HTML source (required)
  html:
    inline: string              # Inline HTML
    fromMember: string          # HTML member name
    template:                   # Template source
      kv: string               # KV key
      r2: string               # R2 key
      inline: string           # Inline template
    data: object               # Template data

  # Page configuration
  page:
    size: string               # A4, A3, A5, Letter, Legal, Tabloid
    orientation: string        # portrait, landscape
    margins:
      top: number             # mm
      right: number
      bottom: number
      left: number
    printBackground: boolean   # Print backgrounds
    scale: number             # 0.1 - 2.0

  # Headers and footers
  headerFooter:
    displayHeaderFooter: boolean
    header: string            # HTML template
    footer: string            # HTML template

  # R2 storage
  storage:
    saveToR2: boolean
    r2Key: string            # R2 object key
    r2Binding: string        # Bucket binding name
    publicUrl: boolean       # Generate public URL

  # Delivery
  deliveryMode: string       # inline or attachment
  filename: string           # Download filename

  # Metadata
  metadata:
    title: string
    author: string
    subject: string
    keywords: string

Output

{
  pdf: ArrayBuffer           // PDF binary
  size: number              // Size in bytes
  url?: string              // Public URL (if stored)
  r2Key?: string            // R2 key (if stored)
  contentDisposition: string // Content-Disposition header
  filename: string          // Suggested filename
  metadata: {
    generateTime: number    // Generation time (ms)
    pageCount?: number      // Number of pages
    htmlSize: number        // HTML source size
  }
}

Best Practices

HTML Design

  1. Use inline styles - External stylesheets may not load
  2. Test in browser first - Preview HTML before PDF generation
  3. Avoid JavaScript - PDFs are static
  4. Use web-safe fonts - Or embed fonts
  5. Page breaks: Use page-break-before: always for new pages

Performance

  1. Cache HTML templates in KV
  2. Reuse HTML member for multiple PDFs
  3. Optimize images - Compress before embedding
  4. Minimize HTML for faster generation

Storage

  1. Organize R2 keys - Use folders: static/invoices/, static/reports/
  2. Include identifiers in filenames - invoice-{number}.pdf
  3. Set public URLs only when needed
  4. Clean up old PDFs periodically

Troubleshooting

PDF Generation Fails

Check:
  1. HTML is well-formed (closing tags, valid structure)
  2. Images use absolute URLs or data URIs
  3. No external resources that can’t load
  4. Page config is valid (margins, scale)

Styles Not Rendering

Solutions:
  1. Use inline <style> tags, not external CSS
  2. Check CSS syntax
  3. Enable printBackground: true for backgrounds
  4. Use !important for critical styles

R2 Storage Errors

Check:
  1. R2 binding exists in wrangler.toml
  2. Binding name matches config (default: ASSETS)
  3. R2 key doesn’t contain .. or start with /
  4. Bucket has sufficient storage

File Too Large

Solutions:
  1. Compress images before embedding
  2. Reduce HTML complexity
  3. Split into multiple PDFs
  4. Optimize CSS (remove unused rules)

Next Steps