Salesforce Visualforce PDF: Convert a Page to PDF with renderAs

Blog / Salesforce · May 24, 2015 · Updated June 10, 2026 · 8 min read
Salesforce Visualforce PDF: Convert a Page to PDF with renderAs

To convert a web page to PDF in Salesforce, build a Visualforce page and add the renderAs="pdf" attribute to the <apex:page> component. Salesforce renders the page's HTML and CSS on the server and returns a downloadable PDF — no browser print dialog, no client-side library, and no JavaScript involved.

The attribute is the whole trick: the same Visualforce markup that displays your records on screen becomes a print-ready document the moment you set renderAs="pdf". You can let users open it from a button, or generate it in Apex and attach or email it automatically. This guide covers the markup, layout with CSS, Apex generation, the rendering-engine change for 2026, and the limits to plan around.

Key takeaways

  • renderAs="pdf" on <apex:page> turns any Visualforce page into a server-rendered PDF.
  • No JavaScript runs during PDF rendering — charts and DOM scripts will not execute, so render data as HTML/CSS or as pre-built images.
  • Control layout with the CSS @page rule for paper size, margins, and running headers/footers.
  • Generate PDFs in Apex with PageReference.getContentAsPDF(), then save them as a ContentVersion (Salesforce Files), a legacy Attachment, or an email attachment.
  • Salesforce now offers an advanced PDF rendering engine with broader CSS support; the original engine is being retired — confirm the exact date in your org's release notes.
  • Plan around the limits: restricted CSS, no scripting, image-access quirks, and a cap on generated PDF size (historically around 15 MB) under standard Apex heap governor limits.

How does renderAs="pdf" work in Salesforce?

Visualforce pages normally render as HTML in the Salesforce UI. When you add renderAs="pdf", Salesforce takes that same HTML output and passes it through a server-side PDF rendering engine instead of returning it to the browser as a web page. The user receives a .pdf file.

Because the conversion happens on the server with no browser involved, you are effectively styling a static document. A minimal page looks like this — note how we also strip the standard Salesforce chrome so it does not bleed into the document:

<apex:page standardController="Contact"
            renderAs="pdf"
            showHeader="false"
            sidebar="false"
            standardStylesheets="false"
            applyHtmlTag="true">

    <h1 style="font-size:20px;">Contact Summary</h1>

    <table style="width:100%; border-collapse:collapse;">
        <tr>
            <th style="text-align:left; padding:6px; border:1px solid #ccc;">Field</th>
            <th style="text-align:left; padding:6px; border:1px solid #ccc;">Value</th>
        </tr>
        <tr>
            <td style="padding:6px; border:1px solid #ccc;">Name</td>
            <td style="padding:6px; border:1px solid #ccc;">
                {!Contact.FirstName} {!Contact.LastName}
            </td>
        </tr>
        <tr>
            <td style="padding:6px; border:1px solid #ccc;">Account</td>
            <td style="padding:6px; border:1px solid #ccc;">{!Contact.Account.Name}</td>
        </tr>
        <tr>
            <td style="padding:6px; border:1px solid #ccc;">Phone</td>
            <td style="padding:6px; border:1px solid #ccc;">{!Contact.Phone}</td>
        </tr>
    </table>

</apex:page>

Open this page with an ?id=<recordId> parameter (for example via a custom button or a Lightning action) and the browser downloads a PDF of the contact. Notice there is no <apex:form> — forms and input components are pointless in a PDF because nothing is interactive once rendered.

Two attributes are worth knowing for finer control:

  • applyHtmlTag and applyBodyTag — when set to false, Salesforce stops auto-wrapping your markup in <html>/<body>, so you can supply your own doctype and structure. This matters most with the advanced rendering engine.
  • renderAs officially accepts "pdf"; the advanced engine is selected through an org setting (see the table further down) rather than ad-hoc syntax in most orgs.

How do you control the PDF layout with CSS?

Paper size, orientation, margins, and page headers/footers are all driven by the CSS @page rule, which only has meaning when a page renders as PDF. Put the CSS in an inline <style> block inside the Visualforce page. Running headers and footers use the CSS paged-media named regions (@top-center, @bottom-center) — older orgs on the legacy engine used Flying Saucer's -fr- extensions for the same effect.

<apex:page standardController="Invoice__c"
            renderAs="pdf"
            showHeader="false"
            standardStylesheets="false">

  <head>
    <style type="text/css" media="print">
      @page {
        size: A4 portrait;          /* or "letter landscape" */
        margin: 25mm 18mm 22mm 18mm;

        @top-center {
          content: "MicroPyramid — Invoice";
          font-size: 10px;
          color: #666;
        }
        @bottom-center {
          /* page X of Y via CSS counters */
          content: "Page " counter(page) " of " counter(pages);
          font-size: 9px;
          color: #666;
        }
      }

      body { font-family: Arial, sans-serif; font-size: 12px; color: #222; }
      h1   { font-size: 18px; }
      .totals { margin-top: 20px; font-weight: bold; }

      /* force a hard page break before a section */
      .page-break { page-break-before: always; }
    </style>
  </head>

  <body>
    <h1>Invoice {!Invoice__c.Name}</h1>
    <p>Billed to: {!Invoice__c.Account__r.Name}</p>
    <!-- line items, totals, etc. -->
    <div class="totals">Total: {!Invoice__c.Total__c}</div>
  </body>

</apex:page>

How do you generate and attach a PDF in Apex?

Letting a user click a button is fine, but most real workflows generate the PDF in Apex and store or send it — for example, snapshotting an invoice when an opportunity closes. PageReference.getContentAsPDF() returns the rendered document as a Blob you can persist or email.

Save it as a modern ContentVersion (Salesforce Files) and link it to the record, or fall back to a legacy Attachment. You can also bolt the Blob onto an email.

public with sharing class ContactPdfService {

    public static void generateAndAttach(Id contactId) {
        // Point at a Visualforce page that uses renderAs="pdf"
        PageReference pdfPage = Page.ContactSummary;
        pdfPage.getParameters().put('id', contactId);

        // getContentAsPDF() throws in test context, so guard it
        Blob pdfBlob = Test.isRunningTest()
            ? Blob.valueOf('Stubbed PDF body for tests')
            : pdfPage.getContentAsPDF();

        // 1) Store as Salesforce Files (ContentVersion + link to the record)
        ContentVersion cv = new ContentVersion(
            Title        = 'Contact Summary',
            PathOnClient = 'ContactSummary.pdf',
            VersionData  = pdfBlob,
            Origin       = 'C'
        );
        insert cv;

        Id docId = [SELECT ContentDocumentId
                    FROM ContentVersion
                    WHERE Id = :cv.Id].ContentDocumentId;

        insert new ContentDocumentLink(
            ContentDocumentId = docId,
            LinkedEntityId    = contactId,
            ShareType         = 'V'
        );

        // 2) (Legacy alternative) save as a classic Attachment:
        // insert new Attachment(ParentId = contactId,
        //                       Name = 'ContactSummary.pdf', Body = pdfBlob);

        // 3) Or email it as an attachment
        Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
        efa.setFileName('ContactSummary.pdf');
        efa.setBody(pdfBlob);

        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setTargetObjectId(contactId);          // a Contact/Lead/User Id
        mail.setSaveAsActivity(false);
        mail.setSubject('Your contact summary');
        mail.setPlainTextBody('Please find the PDF attached.');
        mail.setFileAttachments(new List<Messaging.EmailFileAttachment>{ efa });
        Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail });
    }
}

Legacy vs advanced PDF rendering engine: what changed?

Salesforce historically shipped one PDF rendering engine with fairly dated CSS support. It later introduced an advanced PDF rendering engine with much broader CSS, web-font, and page-header/footer support, and has announced that the original (legacy) engine is being retired. The retirement timeline has shifted across releases, so treat any specific date with caution and confirm it in the current Salesforce release notes for your org before you migrate.

Aspect Original (legacy) engine Advanced rendering engine
CSS support Limited, older CSS subset Broader modern CSS, more reliable layout
Web fonts / Unicode Patchy Better support
Page headers & footers Workaround-heavy First-class via @page CSS
How it's selected Default historically Enabled in Setup (Visualforce PDF settings)
Status in 2026 Being retired — verify date Recommended going forward

The practical advice: build new PDF pages against the advanced engine, and re-test existing pages after enabling it, because CSS that "worked" on the legacy engine can shift slightly.

What are the limitations of Visualforce PDF?

Server-side rendering is powerful but constrained. Design around these before you promise a pixel-perfect document:

  • No JavaScript executes. Anything that depends on client-side scripting — Chart.js, dynamic tables, JS-built layouts — will not render. Compute values in Apex or formulae, or generate charts as static images.
  • CSS is a subset. Flexbox, CSS grid, and some modern selectors are unreliable on the legacy engine; tables and simple block layouts are the safest. The advanced engine narrows this gap.
  • Images and fonts must be reachable and self-contained. Reference static resources or fully qualified URLs; missing or auth-gated images render blank.
  • Size and heap limits. The generated PDF has a maximum size (historically around 15 MB) and runs under standard Apex heap governor limits, so very large documents can fail — confirm current numbers in the Salesforce Governor Limits documentation.
  • Test context. getContentAsPDF() cannot actually render inside a unit test, so stub the Blob when Test.isRunningTest() is true (as shown above).

For complex, design-heavy documents (multi-column brochures, intricate invoices), teams often reach for a dedicated tool instead of fighting the CSS subset.

Visualforce PDF vs other Salesforce PDF options

There is no direct Lightning component equivalent to renderAs="pdf", so a Visualforce PDF page remains the standard built-in approach in 2026. For richer output you can pair it with — or replace it by — other options:

Option Best for Trade-off
Visualforce renderAs="pdf" Standard built-in docs, no extra vendor CSS subset, no JavaScript
AppExchange PDF apps Drag-and-drop templates, complex layouts Adds a managed package and vendor dependency
External rendering service (callout) Pixel-perfect HTML-to-PDF, JS charts Build and maintain an integration; data leaves the org

If you are deciding between these or hardening an existing PDF flow, our Salesforce consulting and development team can help you pick the right path. For tightening the broader build process around customizations like this, see techniques and tools to improve the Salesforce development cycle and our Salesforce customization best practices.

Frequently Asked Questions

Does renderAs="pdf" run JavaScript or render client-side charts?

No. The Salesforce PDF rendering engine processes only HTML and CSS on the server, so no JavaScript executes. Components like Chart.js or any DOM-scripted widget will be blank. Render the data as HTML tables, or generate the chart as a static image (for example a server-produced PNG) and embed that image in the page.

Why is my CSS not working in the Visualforce PDF?

The most common cause is unsupported CSS: the legacy rendering engine only understands an older subset, so flexbox, grid, and some selectors are ignored. Stick to tables and simple block layouts, keep styles inline in a <style> block, set standardStylesheets="false" to stop Salesforce's own CSS interfering, and consider enabling the advanced rendering engine for broader support.

Is the Salesforce legacy PDF rendering engine being deprecated?

Yes. Salesforce has announced that the original PDF rendering engine is being retired in favour of the advanced PDF rendering engine, which has stronger CSS support. The exact cut-off date has moved between releases, so do not rely on a fixed date — check the current Salesforce release notes and your org's Setup notices, and migrate new and existing pages to the advanced engine.

How large a PDF can Visualforce generate?

There is a cap on the size of a PDF produced by getContentAsPDF() (historically around 15 MB), and generation runs under standard Apex heap governor limits, so very large or image-heavy documents can fail. Because limits change between releases, confirm the current figure in the Salesforce Governor Limits documentation and split large documents or downsize images if you hit the ceiling.

Can I generate the PDF in Apex and attach or email it automatically?

Yes. Call PageReference.getContentAsPDF() to get the document as a Blob, then save it as a ContentVersion (Salesforce Files) linked to the record, as a legacy Attachment, or as a Messaging.EmailFileAttachment on an outbound email. Remember that getContentAsPDF() cannot render inside a unit test, so stub the Blob when Test.isRunningTest() is true.

Does Lightning Experience have a replacement for Visualforce PDF?

Not directly. Lightning Web Components have no built-in server-side HTML-to-PDF feature, so the Visualforce page with renderAs="pdf" remains the standard native approach and works fine alongside Lightning. For complex, design-heavy documents many teams add an AppExchange PDF app or call an external rendering service instead.

Share this article