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
@pageCSS 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:
- Go to Setup → Visualforce Pages (or use the Developer Console).
- Click New and give your page a name (e.g.,
InvoicePDF). - Set the Standard Controller to the object your PDF will pull data from (usually Opportunity, Quote, or a custom object).
- Add
renderAs="pdf"to generate PDF output. - Add
applyBodyTag="false"andapplyHtmlTag="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>
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)
@pagefor page size, margins, and orientationpage-break-beforeandpage-break-afterfor multi-page documents- Background colors and simple borders
What doesn’t work:
display: flexanddisplay: 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:
- Preview in browser: Navigate to
/apex/YourPageName?id=RECORD_IDto see the PDF render. ReplaceRECORD_IDwith an actual Opportunity or Quote ID. - Check page breaks: If your document can span multiple pages, add enough test data to trigger a second page. Use
page-break-inside: avoidon table rows to prevent awkward splits. - Test with edge cases: Long company names, missing fields, zero line items, special characters (&, <, >).
- Check formatting: Currency fields should use
<apex:outputText>with format patterns, not raw merge fields. - 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:
- Add a button to the record page. Create a custom button or action that links to
/apex/YourPageName?id={!Opportunity.Id}. - Attach to emails. Use
Messaging.SingleEmailMessagein Apex to generate the PDF and attach it to an outbound email automatically. - 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).
- 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;
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.