PDF Headers and Footers with Visualforce

Over the past couple of months, I’ve seen several posts on the Developer forums asking how to set PDF headers and footers with Visualforce.  I decided to sit down and try my hand at it.  If you have done the Battle Station app on Trailhead, you can try this out on your own sandbox!  We will be generating a PDF invoice for the Battle Station app that includes a first page header image and a dynamically generated footer.

Preqisites

  • Complete Battle Station App
  • Add new Roll-Up summary called Total_Cost__c to the Battle_Station__c object that SUMs the Total Cost fields on the Supply__c object
  • Add this image as a static resource called BattleStationHeader

PDF Design

Basic PDF Rendering

Let’s start off with the most basic of PDFs we can generate, just to show that we can display all the data we want to show on our invoice.  The Visualforce below simply renders out the Battle Station information as well as it’s Resources and Supplies.

<apex:page standardController="Battle_Station__c" renderAs="pdf">
    <h1>{!Battle_Station__c.Name}</h1>
    <apex:pageBlock >
        <apex:pageBlockSection columns="1">
            <apex:outputText value="{!Battle_Station__c.Project_Status__c}" />
            <apex:outputText value="{!Battle_Station__c.Weapons_Status__c}" />
        </apex:pageBlockSection>
        <h2>{!$ObjectType.Resource__c.labelPlural}</h2>
        <apex:pageBlockSection columns="1">
            <apex:pageBlockTable value="{!Battle_Station__c.Resources__r}" var="resource">
                <apex:column value="{!resource.Name}" />
                <apex:column value="{!resource.Quantity__c}" />
                <apex:column value="{!resource.Utilization__c}" />
            </apex:pageBlockTable>
        </apex:pageBlockSection>
        <h2>{!$ObjectType.Supply__c.labelPlural}</h2>
        <apex:pageBlockSection columns="1">
            <apex:pageBlockTable value="{!Battle_Station__c.Supplies__r}" var="supply">
                <apex:column value="{!supply.Name}" />
                <apex:column value="{!supply.Quantity__c}" />
                <apex:column value="{!supply.Unit_Cost__c}" />
                <apex:column value="{!supply.Total_Cost__c}" />
            </apex:pageBlockTable>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

Version 1

The resulting PDF is surely functional.  We can see all of the data.  However it’s not much to look at.  And if I was a customer of this company, I would think that they do not know what they are doing if they were to hand me an invoice like this.

Stylize the basic PDF

By adding some basic CSS and updating some of the classes on the Visualforce elements, we can generate a much better looking PDF.

<apex:page standardController="Battle_Station__c" renderAs="pdf" applyBodyTag="false">
    <head>
        <style type="text/css" media="print">           
            .stationName {
                text-align: center;
            	font-weight: bold;
            	font-size: 20pt;
            	margin-bottom: 30px;
            }
            
            table {
            	width: 100%;
            }
            
            .centered {
            	text-align: center;
            }
            
            .right {
            	text-align: right;
            }
            
            .tableHeader {
            	border-width: 0px 0px 1px 0px;
            	border-color: #000;
            	border-style: solid;
            }
            
            .sectionHeader {
            	width: 100%;
            	background-color: #eee;
            	font-size: 16pt;
            	padding: 5px;
            	margin: 20px 0px;
            	font-weight: bold;
            }
        </style>
    </head>
    <h1 class="stationName">{!Battle_Station__c.Name}</h1>
    <apex:pageBlock >
        <apex:pageBlockSection columns="1">
            <apex:outputText value="{!Battle_Station__c.Project_Status__c}" />
            <apex:outputText value="{!Battle_Station__c.Weapons_Status__c}" />
        </apex:pageBlockSection>
        <div class="sectionHeader">{!$ObjectType.Resource__c.labelPlural}</div>
        <apex:pageBlockSection columns="1">
            <apex:pageBlockTable value="{!Battle_Station__c.Resources__r}" var="resource" headerClass="tableHeader">
                <apex:column value="{!resource.Name}" />
                <apex:column value="{!resource.Quantity__c}" headerClass="centered" styleClass="centered" />
                <apex:column value="{!resource.Utilization__c}" headerClass="centered" styleClass="centered" />
            </apex:pageBlockTable>
        </apex:pageBlockSection>
        <div class="sectionHeader">{!$ObjectType.Supply__c.labelPlural}</div>
        <apex:pageBlockSection columns="1">
            <apex:pageBlockTable value="{!Battle_Station__c.Supplies__r}" var="supply" headerClass="tableHeader">
                <apex:column value="{!supply.Name}" />
                <apex:column value="{!supply.Quantity__c}" headerClass="centered" styleClass="centered" />
                <apex:column value="{!supply.Unit_Cost__c}" headerClass="right" styleClass="right" />
                <apex:column value="{!supply.Total_Cost__c}" headerClass="right" styleClass="right" />
            </apex:pageBlockTable>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

Version 2

This rendered PDF is presentable.  It doesn’t have a tons of personality, but it gets the point across and shows all the data in a clearly formatted way.  But we can do better.

Adding PDF Headers and Footers

By using CSS selectors, we can add a print header and footer that will show up in their respective parts of the PDF.  The key parts of this are the header div (empty here because we’re using a Static Resource as our background image), and the footer div.  The content div and the other CSS is just to improve some spacing.

<apex:page standardController="Battle_Station__c" renderAs="pdf" applyBodyTag="false">
    <head>
        <style type="text/css" media="print">
            @page {
                @top-center {
                    content: element(header);
                }
    
                @bottom-left {
                    content: element(footer);
                }
            }
            
            * {
            	margin: 0px;
            	padding: 0px;
            }
            
            div.header {
            	background: url("{!$Resource.BattleStationHeader}") no-repeat center center;
            	margin-top: 30px;
            	height: 130px;
            	width: 715px;
            	text-align: center;
            	position: running(header);
            }
            
            div.content {
            	padding-top: 130px;
            }
            
            div.footer {
            	display: block;
            	padding: 5px;
                position: running(footer);
            }
            
            div.subfooter {
            	display: inline-block;
            }
            
            div.right {
            	float: right;
            }
            
            .pagenumber:before {
                content: counter(page);
            }
            
            .pagecount:before {
            	content: counter(pages);
            }
            
            .stationName {
                text-align: center;
            	font-weight: bold;
            	font-size: 20pt;
            	margin-bottom: 30px;
            }
            
            table {
            	width: 100%;
            }
            
            .centered {
            	text-align: center;
            }
            
            .right {
            	text-align: right;
            }
            
            .tableHeader {
            	border-width: 0px 0px 1px 0px;
            	border-color: #000;
            	border-style: solid;
            }
            
            .sectionHeader {
            	width: 100%;
            	background-color: #eee;
            	font-size: 16pt;
            	padding: 5px;
            	margin: 20px 0px;
            	font-weight: bold;
            }
            
            #totalCost {
            	margin-top: 15px;
            }
            
            #totalCostLabel {
            	font-weight: bold;
            	margin-right: 10px;
            }
        </style>
    </head>
    <div class="header"></div>
    <div class="content">
        <h1 class="stationName">{!Battle_Station__c.Name}</h1>
        <apex:pageBlock >
            <apex:pageBlockSection columns="1">
                <apex:outputText value="{!Battle_Station__c.Project_Status__c}" />
                <apex:outputText value="{!Battle_Station__c.Weapons_Status__c}" />
            </apex:pageBlockSection>
            <div class="sectionHeader">{!$ObjectType.Resource__c.labelPlural}</div>
            <apex:pageBlockSection columns="1">
                <apex:pageBlockTable value="{!Battle_Station__c.Resources__r}" var="resource" headerClass="tableHeader">
                    <apex:column value="{!resource.Name}" />
                    <apex:column value="{!resource.Quantity__c}" headerClass="centered" styleClass="centered" />
                    <apex:column value="{!resource.Utilization__c}" headerClass="centered" styleClass="centered" />
                </apex:pageBlockTable>
            </apex:pageBlockSection>
            <div class="sectionHeader">{!$ObjectType.Supply__c.labelPlural}</div>
            <apex:pageBlockSection columns="1">
                <apex:pageBlockTable value="{!Battle_Station__c.Supplies__r}" var="supply" headerClass="tableHeader">
                    <apex:column value="{!supply.Name}" />
                    <apex:column value="{!supply.Quantity__c}" headerClass="centered" styleClass="centered" />
                    <apex:column value="{!supply.Unit_Cost__c}" headerClass="right" styleClass="right" />
                    <apex:column value="{!supply.Total_Cost__c}" headerClass="right" styleClass="right" />
                </apex:pageBlockTable>
            </apex:pageBlockSection>
            <div id="totalCost" class="right"><span id="totalCostLabel">{!$ObjectType.Battle_Station__c.Fields.Total_Cost__c.Label}:</span>          <apex:outputField value="{!Battle_Station__c.Total_Cost__c}"/></div>
        </apex:pageBlock>
    </div>
    <div class="footer">
        <div class="centered">Generated by {!$User.FirstName} {!$User.LastName}</div>
        <div>
            <div class="subfooter">{!NOW()}</div>
            <div class="subfooter right">Page <span class="pagenumber"/> of <span class="pagecount"/></div>
        </div>
    </div>
</apex:page>

Version 3 - PDF Headers and Footers

Now, this PDF looks great.  Just adding the header makes this pop so much.  The footer also gives us room to put additional information such as when it was generated and how many pages there are.

Whoops! Multiple pages

Our PDF above looks great, let’s call it done.  Well, before we ship it off, we better try adding enough data to see what happens when it spans multiple pages.

Version 4 Page 1

so far so good

Version 4 Page 1

Oh man, our beautiful PDF looks terrible now.

Fixing the Multiple Page Issue

Fortunately, this fix is purely a CSS fix and it’s pretty straight-forward.  The current CSS tells the PDF renderer to put the header on all pages.  We want this to only be on the first page.  So we need to update our CSS only apply the header on the first page, but still put the footer on all pages

@page :first {
    @top-center {
        content: element(header);
    }
}
            
@page {    
    @bottom-left {
        content: element(footer);
    }
}

Version 5 Page 1

now, the first page still looks the same

Version 5 Page 2
and now our final PDF looks great!

Final Code

Now if we put it all together, our Visualforce page contains the following

<apex:page standardController="Battle_Station__c" renderAs="pdf" applyBodyTag="false">
    <head>
        <style type="text/css" media="print">
            @page :first {
                @top-center {
                    content: element(header);
                }
            }
            
            @page {    
                @bottom-left {
                    content: element(footer);
                }
            }
            
            * {
            	margin: 0px;
            	padding: 0px;
            }
            
            div.header {
            	background: url("{!$Resource.BattleStationHeader}") no-repeat center center;
            	margin-top: 30px;
            	height: 130px;
            	width: 715px;
            	text-align: center;
            	position: running(header);
            }
            
            div.content {
            	padding-top: 130px;
            }
            
            div.footer {
            	display: block;
            	padding: 5px;
                position: running(footer);
            }
            
            div.subfooter {
            	display: inline-block;
            }
            
            div.right {
            	float: right;
            }
            
            .pagenumber:before {
                content: counter(page);
            }
            
            .pagecount:before {
            	content: counter(pages);
            }
            
            .stationName {
                text-align: center;
            	font-weight: bold;
            	font-size: 20pt;
            	margin-bottom: 30px;
            }
            
            table {
            	width: 100%;
            }
            
            .centered {
            	text-align: center;
            }
            
            .right {
            	text-align: right;
            }
            
            .tableHeader {
            	border-width: 0px 0px 1px 0px;
            	border-color: #000;
            	border-style: solid;
            }
            
            .sectionHeader {
            	width: 100%;
            	background-color: #eee;
            	font-size: 16pt;
            	padding: 5px;
            	margin: 20px 0px;
            	font-weight: bold;
            }
            
            #totalCost {
            	margin-top: 15px;
            }
            
            #totalCostLabel {
            	font-weight: bold;
            	margin-right: 10px;
            }
        </style>
    </head>
    <div class="header"></div>
    <div class="content">
        <h1 class="stationName">{!Battle_Station__c.Name}</h1>
        <apex:pageBlock >
            <apex:pageBlockSection columns="1">
                <apex:outputText value="{!Battle_Station__c.Project_Status__c}" />
                <apex:outputText value="{!Battle_Station__c.Weapons_Status__c}" />
            </apex:pageBlockSection>
            <div class="sectionHeader">{!$ObjectType.Resource__c.labelPlural}</div>
            <apex:pageBlockSection columns="1">
                <apex:pageBlockTable value="{!Battle_Station__c.Resources__r}" var="resource" headerClass="tableHeader">
                    <apex:column value="{!resource.Name}" />
                    <apex:column value="{!resource.Quantity__c}" headerClass="centered" styleClass="centered" />
                    <apex:column value="{!resource.Utilization__c}" headerClass="centered" styleClass="centered" />
                </apex:pageBlockTable>
            </apex:pageBlockSection>
            <div class="sectionHeader">{!$ObjectType.Supply__c.labelPlural}</div>
            <apex:pageBlockSection columns="1">
                <apex:pageBlockTable value="{!Battle_Station__c.Supplies__r}" var="supply" headerClass="tableHeader">
                    <apex:column value="{!supply.Name}" />
                    <apex:column value="{!supply.Quantity__c}" headerClass="centered" styleClass="centered" />
                    <apex:column value="{!supply.Unit_Cost__c}" headerClass="right" styleClass="right" />
                    <apex:column value="{!supply.Total_Cost__c}" headerClass="right" styleClass="right" />
                </apex:pageBlockTable>
            </apex:pageBlockSection>
            <div id="totalCost" class="right"><span id="totalCostLabel">{!$ObjectType.Battle_Station__c.Fields.Total_Cost__c.Label}:</span> <apex:outputField value="{!Battle_Station__c.Total_Cost__c}"/></div>
        </apex:pageBlock>
    </div>
    <div class="footer">
        <div class="centered">Generated by {!$User.FirstName} {!$User.LastName}</div>
        <div>
            <div class="subfooter">{!NOW()}</div>
            <div class="subfooter right">Page <span class="pagenumber"/> of <span class="pagecount"/></div>
        </div>
    </div>
</apex:page>

Take Aways

Generating PDFs in Salesforce with Visualforce isn’t that difficult.  With a little bit of CSS and some patience, you can do it.  Just remember:

  • Build your PDF in stages.  Don’t try to get it all right the first shot, it’s much easier to gradually build it then try to troubleshoot all the CSS at once.
  • Test it with large data sets. Take the time to add more data than you normally would to make sure that things don’t break terribly.
  • Test with multiple browsers and view settings.  The PDF renderer is very susceptible to making your painstakingly design PDF look off when the user’s browser preference are off.  Test it out using multiple browsers with different settings.  Zoom in on the page before generating the PDF and check the font size.  You can combat this by specifying all of your fonts and font sizes in your CSS.  It’ll take more time, but you’ll end up with a better product.
This entry was posted in Development, Salesforce and tagged , , , . Bookmark the permalink.
  • Melissa de Pau

    How can I include the table header on the subsequent pages?

  • If you remove :first from the @page stanza in the CSS it will be applied to all pages

  • Melissa de Pau

    That’s definitely how you include the page header, but I’m referring to the table header, such as “Supply… Quantity… etc.” I seem to be stuck on this user requirement.

  • I did try to figure this out for a little while and then moved on to other things. I’ll try to figure out how to make the table header repeat per page and will let you know.

  • Welp, in my outputted PDF it’s not there and it should be. I’ll review the code and see where it’s not working and will get back to you.

  • Suresh Reddy

    i want different herder each page ex(i had four pages four different headers )

  • You’d have to have it at four different pages with each having their own their own CSS styling. Then I’d combine them together using the apex:include. There may have to be some extra CSS hacking to make sure they all show up on individual pages.

  • Suresh Reddy

    please send any example

  • Douglas Ayers

    Thanks for the great write-up and example Patrick!

    Regarding the footers, I had to define the content within the @page CSS tags themselves rather than reference HTML elements:

    @page {

    @bottom-left {
    content: “{!NOW()}”
    }

    @bottom-right {
    content: counter(page) ” / ” counter(pages);
    }

    }

    Reference: https://stackoverflow.com/a/20050986/470818

  • Sean

    Was this ever identified?

  • I you look at @douglasayers:disqus comment (that I just published) you should see the fix. I’ll get this added to the post ASAP as well.

  • Nirav Shah

    How to give the Custom Title to PDF. Currently the PDF Page shows the title as “APEX Page Name” which I want to ovverride

  • There doesn’t seem to be a way to do it. It will always use the Visualforce page name as the Tab’s title