Guide

Build Salesforce PDFs with Visualforce

From blank page to production-ready invoices and quotes — everything you need to know about Visualforce PDF generation.

How Visualforce PDF Generation Works

Salesforce can render any Visualforce page as a PDF by adding renderAs="pdf" to the <apex:page> tag. Under the hood, Salesforce uses a headless browser engine to convert your HTML/CSS into a PDF document.

Key things to know:

  • The PDF renderer supports a subset of CSS — roughly equivalent to what worked in browsers circa 2012.
  • Flexbox and Grid do not work. Use tables for layout.
  • The @page CSS rule controls page size, margins, and orientation.
  • You have full access to Salesforce merge fields — pull any data from the controller’s object.
  • PDFs can be generated on-demand (button click), attached to emails, or created automatically via triggers.
<!-- Minimal Visualforce PDF -->
<apex:page standardController="Opportunity" renderAs="pdf">
  <h1>Hello, {!Opportunity.Name}</h1>
  <p>Amount: {!Opportunity.Amount}</p>
</apex:page>

Setting Up Your First PDF Template

Let’s create a basic invoice PDF step by step:

  1. Go to Setup → Visualforce Pages (or use the Developer Console).
  2. Click New and give your page a name (e.g., InvoicePDF).
  3. Set the Standard Controller to the object your PDF will pull data from (usually Opportunity, Quote, or a custom object).
  4. Add renderAs="pdf" to generate PDF output.
  5. Add applyBodyTag="false" and applyHtmlTag="false" for full control over the HTML structure.

With these attributes, you have a blank canvas — write standard HTML and CSS, and Salesforce renders it as a downloadable PDF.

<apex:page standardController="Opportunity"
         renderAs="pdf"
         applyBodyTag="false"
         applyHtmlTag="false">
<html>
<head>
  <style type="text/css">
    @page { size: letter; margin: 0.75in; }
    body { font-family: Helvetica, sans-serif; font-size: 12px; color: #333; }
    h1 { color: #5235ef; font-size: 20px; }
  </style>
</head>
<body>
  <h1>Invoice</h1>
  <p>Prepared for: {!Opportunity.Account.Name}</p>
</body>
</html>
</apex:page>
Skip the code: Use our PDF Builder to design your template visually and get the Visualforce code generated for you.

Styling Your PDF

PDF styling in Visualforce has some important differences from regular web CSS:

What works:

  • Basic CSS properties: fonts, colors, margins, padding, borders
  • Tables for layout (the most reliable approach)
  • @page for page size, margins, and orientation
  • page-break-before and page-break-after for multi-page documents
  • Background colors and simple borders

What doesn’t work:

  • display: flex and display: grid — use tables instead
  • Web fonts (Google Fonts, etc.) — stick to system fonts
  • CSS variables
  • Complex selectors and pseudo-elements
  • JavaScript (the page is rendered server-side)

Recommended fonts: Helvetica, Arial, Georgia, Times New Roman, Courier New. These are guaranteed to render correctly.

/* Layout with tables instead of flexbox */
<style>
  .header-table { width: 100%; }
  .header-table td { vertical-align: top; }
  .header-left { width: 60%; }
  .header-right { width: 40%; text-align: right; }
</style>

<table class="header-table">
  <tr>
    <td class="header-left">
      <h1>Invoice</h1>
      <p>{!Account.Name}</p>
    </td>
    <td class="header-right">
      <p>Invoice #: {!Opportunity.Name}</p>
      <p>Date: <apex:outputText value="{0, date, MM/dd/yyyy}">
        <apex:param value="{!TODAY()}" />
      </apex:outputText></p>
    </td>
  </tr>
</table>

Tables and Iteration

Most PDF documents need a table of line items. Use <apex:repeat> to loop through related records:

<table style="width:100%; border-collapse:collapse;">
  <thead>
    <tr style="background:#5235ef; color:#fff;">
      <th style="padding:8px; text-align:left;">Product</th>
      <th style="padding:8px; text-align:right;">Qty</th>
      <th style="padding:8px; text-align:right;">Unit Price</th>
      <th style="padding:8px; text-align:right;">Total</th>
    </tr>
  </thead>
  <tbody>
    <apex:repeat value="{!Opportunity.OpportunityLineItems}" var="item">
      <tr>
        <td style="padding:8px; border-bottom:1px solid #eee;">
          {!item.PricebookEntry.Product2.Name}
        </td>
        <td style="padding:8px; border-bottom:1px solid #eee; text-align:right;">
          {!FLOOR(item.Quantity)}
        </td>
        <td style="padding:8px; border-bottom:1px solid #eee; text-align:right;">
          <apex:outputText value="{0, number, $#,##0.00}">
            <apex:param value="{!item.UnitPrice}" />
          </apex:outputText>
        </td>
        <td style="padding:8px; border-bottom:1px solid #eee; text-align:right;">
          <apex:outputText value="{0, number, $#,##0.00}">
            <apex:param value="{!item.TotalPrice}" />
          </apex:outputText>
        </td>
      </tr>
    </apex:repeat>
  </tbody>
</table>

<!-- Totals -->
<table style="margin-left:auto; margin-top:20px;">
  <tr>
    <td style="padding:4px 12px;">Subtotal</td>
    <td style="padding:4px 12px; text-align:right;">
      <apex:outputText value="{0, number, $#,##0.00}">
        <apex:param value="{!Opportunity.Amount}" />
      </apex:outputText>
    </td>
  </tr>
</table>

Testing Your PDF

Test your Visualforce PDF before deploying:

  1. Preview in browser: Navigate to /apex/YourPageName?id=RECORD_ID to see the PDF render. Replace RECORD_ID with an actual Opportunity or Quote ID.
  2. Check page breaks: If your document can span multiple pages, add enough test data to trigger a second page. Use page-break-inside: avoid on table rows to prevent awkward splits.
  3. Test with edge cases: Long company names, missing fields, zero line items, special characters (&, <, >).
  4. Check formatting: Currency fields should use <apex:outputText> with format patterns, not raw merge fields.
  5. Cross-browser note: The PDF looks the same regardless of the user’s browser — Salesforce renders it server-side.

Common issues:

  • Blank PDF: Usually a Visualforce compilation error. Check the developer console for errors.
  • Missing data: Ensure your controller has access to all related objects. You may need to add relationships to the query.
  • Layout broken: Remember — no flexbox. Use tables for all two-column layouts.

Deploying to Production

Once your PDF template is working, deploy it for users:

  1. Add a button to the record page. Create a custom button or action that links to /apex/YourPageName?id={!Opportunity.Id}.
  2. Attach to emails. Use Messaging.SingleEmailMessage in Apex to generate the PDF and attach it to an outbound email automatically.
  3. Trigger-based generation. Use an Apex trigger to auto-generate PDFs when a record hits a specific stage (e.g., "Closed Won" creates an invoice).
  4. Save as attachment. Use PageReference.getContentAsPDF() in Apex to save the rendered PDF as an Attachment or ContentVersion on the record.
// Apex: Generate PDF and save as file on the record
PageReference pdfPage = Page.InvoicePDF;
pdfPage.getParameters().put('id', opp.Id);

Blob pdfBlob;
if (Test.isRunningTest()) {
    pdfBlob = Blob.valueOf('Test PDF');
} else {
    pdfBlob = pdfPage.getContentAsPDF();
}

ContentVersion cv = new ContentVersion();
cv.Title = 'Invoice - ' + opp.Name;
cv.PathOnClient = 'Invoice.pdf';
cv.VersionData = pdfBlob;
cv.FirstPublishLocationId = opp.Id;
insert cv;
Want us to build this for you? We can set up full document automation — auto-generate, email, and archive PDFs from your Salesforce workflow. Let’s talk.

Need More Than a Template?

We can build full document automation — auto-generate, e-sign, and archive PDFs triggered by your Salesforce workflow.

Document Automation

Auto-generate quotes, invoices, and contracts when deals hit specific stages — no manual PDF creation.

E-Signature Workflows

Send generated PDFs for electronic signature via DocuSign or Adobe Sign, tracked right in Salesforce.

Salesforce CPQ

Complex quoting with product bundles, volume discounts, and approval chains — beyond what Visualforce PDFs can do alone.

Talk to Us