Add a Save as PDF Feature to a Visualforce Page

You can add a Save as PDF element to a page to dynamically toggle between rendering the page as HTML or as a PDF file. You can also set the name for the PDF file.
The following page presents a list of contacts for an account. You can display it on screen, or download it as a PDF file by clicking the Save to PDF link.
<apex:page showHeader="false" standardStylesheets="false"
    standardController="Account" extensions="SaveAsPdfExtension"
    contentType="{! renderedContentType }" renderAs="{! renderingService }">

    <!-- 
    This page must be called with an Account ID in the URL, e.g.:
    https://<salesforceInstance>/apex/AccountContactsPdf?id=001D000000JRBet
    -->
        
    <apex:form rendered="{! renderingService != 'PDF' }"
               style="text-align: right; margin: 10px;">
        <apex:commandLink action="{! saveToPdf }" value="Save to PDF">
            <apex:param assignTo="{! renderedFileName }" value="Contact-List.pdf"/>
        </apex:commandLink>
        <hr/>
    </apex:form>
    
    <h1>Contacts for {! Account.Name}</h1>
    
    <apex:dataTable value="{! Account.Contacts }" var="contact">
        <apex:column headerValue="Name"  value="{! contact.Name  }"/>
        <apex:column headerValue="Title" value="{! contact.Title }"/>
        <apex:column headerValue="Phone" value="{! contact.Phone }"/>
        <apex:column headerValue="Email" value="{! contact.Email }"/>
    </apex:dataTable>

    <hr/>
    <!-- A little bit of info about the page's rendering; 
         see how it changes when saved as a PDF. -->
    contentType: <apex:outputText value=" {! renderedContentType }"/><br/>
    renderingService: <apex:outputText value=" {! renderingService }"/><br/>
</apex:page>

This example has two important elements. First, the renderAs and contentType attributes of the <apex:page> component are set dynamically using expressions. The values of these expressions control into which format the page is rendered.

The other element is the <apex:form>, which provides a user interface for saving the page to PDF. The form has one element, an <apex:commandLink> that calls the saveToPdf action method. An <apex:param> component provides a name for the PDF file, which is used in the controller code to set the file name.

The form is only displayed when the page is rendered as HTML; it’s not visible in the PDF version. This display trick is accomplished by setting the rendered attribute on the <apex:form> component to false when the page is rendered as a PDF file.

Here’s the controller extension, which you can easily reuse in your own pages.
public class SaveAsPdfExtension {

    // Required extension constructor (empty, no-op)
    public SaveAsPDFExtension(ApexPages.StandardController controller) {}
    
    // Determines what kind of rendering to use for the page request
    public String renderingService { get; private set; }
    
    // Allow the page to set the PDF file name
    public String renderedFileName { 
        get; 
        set { renderedFileName = this.sanitizeFileName(value); }
    }

    // Rendered content MIME type, used to affect HTTP response
    public String renderedContentType {
        get {
            String renderedContentType = 'text/html'; // the default
            
            if( ! this.renderingAsHtml() ) {
                // Provides a MIME type for a PDF document 
                renderedContentType = 'application/pdf';
                
                // Add a file name for the PDF file
                if( this.renderedFileName != null) {
                    // This is supposed to set the file name, but it doesn't work
                    renderedContentType += '#' + this.renderedFileName;
                    
                    // This is a work-around to set the file name
                    ApexPages.currentPage().getHeaders().put(
                        'content-disposition', 'attachment; filename=' + 
                         this.renderedFileName);
                }
            }
            
            return renderedContentType;
        }
    }
    
    // Are we rendering to HTML or PDF?
    public Boolean renderingAsHtml() {
        return ( (renderingService == null) || 
                 ( ! renderingService.startsWith('PDF')) );
    }

    // Action method to save (or "print") to PDF
    public PageReference saveToPdf() {
        renderingService = 'PDF';
        return null;
    }
    
    // Private helper -- basic, conservative santization
    private String sanitizeFileName(String unsafeName) {
        String allowedCharacters = '0-9a-zA-Z-_.';
        String sanitizedName = 
            unsafeName.replaceAll('[^' + allowedCharacters + ']', '');
        // You might also want to check filename length, 
        // that the filename ends in '.pdf', etc.
        return(sanitizedName);
    }
}

The main part of the extension is simple. The renderingService property controls whether the page is rendered in HTML or PDF. Its value defaults to null when the page is loaded, and changes to “PDF” if the saveToPdf action method is called. The renderAs attribute of the <apex:page> component references renderingService. When it’s anything other than “PDF” the page renders normally as HTML. When it’s “PDF” the page—you guessed it—renders as a PDF file.

The renderedContentType property provides a MIME type value that is used by the contentType attribute of the Visualforce <apex:page> component. Setting this value affects the server response. It adds an HTTP header that tells the client browser the format of the response—in this case, either HTML or PDF.

The renderedContentType property also sets the file name for the downloaded PDF file. It gets the file name from the renderedFileName property, which is set using the <apex:param> component in the page. Although it’s documented that appending “#” and a file name to the contentType sets the file name that’s sent to the client browser, this convention doesn’t work. Therefore, a header is set to provide the file name.

If you don’t need to set the file name for the PDF download, you can ignore the renderedContentType and renderedFileName properties. This simpler approach to adding a save to PDF function is demonstrated in Fonts Available When Using Visualforce PDF Rendering.

Previous
Next