In this page

Customizing PDF content, look and layout

Overview

PDF document content, look and page layout is defined by PDF template files named something-fo.vm. (There can be any number of these, but more on that later.)

The -fo.vm suffix denotes that these files are Velocity / FO templates. It means that those are a plain text file written in the Velocity template language, which is then transformed into FO documents, which is finally transformed to PDF. See more about this process in the next section.

Although both of those languages are very easy to grasp without any further reading, you may want to familiarize yourself with the basics of the Velocity template language syntax and the FO syntax. Where to start?

  1. You can probably learn most just by looking at the sample template files shipped with the app. Not only they are useful as is, they can also be great starting points to develop totally new templates.
  2. The best documentation available on the simple, yet powerful Velocity language is the Velocity User Guide. It is illustrated with great examples.
  3. Various XSL-FO tutorials are available on the web (just Google for "xsl fo tutorial"). Start with reading this tutorial if you want to pick up the FO basics quickly.

Editing PDF templates

PDF templates are editable through a convenient web based management interface, called the Template Manager. Go to Administration → Add-ons → PDF Templates in Jira 6, and to Administration → PDF Templates in Jira 5, to see the filelist. Please note that it includes not only templates, but also Groovy scripts and other resources available for the PDF renderer.

Click any of the filenames to edit it right in your browser! The editor allows modifying the filename and the description of the file, and edit its actual content. It comes with syntax highlighting, line numbering and other convenience features you would expect from a code editor.

It is also important to know that all these files are saved with the standard Jira backup mechanism, for the safety of your templates.

The PDF file rendering process

The two-stage PDF rendering process works like this:

  1. First, issue-fo.vm is rendered through Apache Velocity: it iterates over the issues, replaces the variable references (like $issue.description or $issue.resolutionObject.name) by their actual values, executes scripts and so on. The result of this stage is a temporary in-memory FO document.
  2. Then, the resulted FO document is transformed into PDF by Apache FOP. The result is the actual final PDF document.

Data model

Velocity objects for templates

During the first pass ("Velocity rendering"), you can access the following domain objects in the Velocity context:

Domain Object Description
$ctx The Velocity context itself.
You will mostly need it when you want to include other templates using the $include tool.
$user User instance representing the currently signed-in user, i.e. the user who initiated the export.
$i18n I18nHelper instance that allows accessing internationalized Jira texts (like field names) while rendering.
$pdfView Represents the "export type" which was selected in the Export menu to initiate the document generation.
You can access its public properties by the expressions ${pdfView.name}, ${pdfView.description} and ${pdfView.templateName}.
(since 3.0.0.)
$currentDate Date of the document generation.
$title String title for the document.
See this section for details.
$issues The Collection that stores the Issue objects to export. It contains only one item if the app was invoked from the Issue Details screen (single issue export), or multiple item when the app was invoked from Issue Navigator (multiple issue export).
You can iterate through this collection using the #foreach directive.
$searchRequest SearchRequest that is available only if the document is generated from a "search": browsing the issues with the Issue Navigator, executing filters, running free text searches, etc.
Not available for single issue exports.

You are, of course, not limited to using these objects only. You can access tons of other information by navigating from these starting points in the object graph.

You can, for instance, retrieve the enclosing project and its latest version with the following Velocity code:

#if($issues.size() != 0)
	#set($project = $issues.get(0).projectObject)
	#if($project.versions.size() != 0)
		#set($lastVersionIndex = $project.versions.size() - 1)
		#set($version = $project.versions.get($lastVersionIndex))
	#end
#end

Velocity tools for templates

First, you can use the same tools that are available for Jira email templates also in PDF templates. These offer basic functionality for formatting, escaping and other common use cases.

You can additionally use:

Component / Tool Description
$changeHistoryManager ChangeHistoryManager returns the issue field value changes by various criteria.
Use this to export issue updates, workflow transition histories or metrics calculated from those.

Groovy example:
def changeHistories = changeHistoryManager.getChangeHistoriesForUser(issue, user)
$columnLayoutManager ColumnLayoutManager gives access to various Issue Navigator column layouts: a saved filter's or search's own column layout, the current user's own column layout, and the system default column layout.
This manager is particularly useful if you want to export an issue list, but reusing the column layout definition the user configured in Issue Navigator.
$commentManager CommentManager is used to retrieve the issue comments.

Groovy example:
def comments = commentManager.getCommentsForUser(issue, user)
$componentAccessor ComponentAccessor can be used to get references to $projectManager, $userManager, $permissionManager and other Jira core components, that are not directly available in the Velocity context.
You will use this when developing templates and scripts that require components not listed in this table.

Velocity example:
#set($projectManager = $componentAccessor.projectManager)
$componentManager ComponentManager is the deprecated (legacy) way to get references to Jira core components.
You should normally use $componentAccessor (see the previous item), and revert to using this only in Jira versions prior to 5.2.
Completely removed in app version 6.3.0.
$customFieldManager CustomFieldManager supports working with custom fields.

Velocity example:
#set($customFields = $customFieldManager.getCustomFieldObjects($issue))
$fieldScreenRendererFactory FieldScreenRendererFactory allows you to obtain field screen renderers, to check which custom fields are added to what screens and tabs.
(Since app version 3.3.0.)
$fieldVisibilityManager FieldVisibilityManager returns whether a custom field is visible for an issue.

Velocity example:
#if($fieldVisibilityManager.isFieldVisible('versions', $issue)) ... #end
(Since app version 3.3.0.)
$issueLinkManager IssueLinkManager can return the inward / outward issue links (for example, "duplicated by" and "duplicates").

Groovy example:
def linkCollection = issueLinkManager.getLinkCollection(issue, user)
$issueViewUtil IssueViewUtil is legacy collection of utility methods.
All functionality offered by this is now available via "smarter" objects in the Velocity context.
$jiraDurationUtils JiraDurationUtils export nice time duration strings in long- ("2 days 5 hours") or compact format ("2d 5h").
$remoteIssueLinkManager RemoteIssueLinkManager gives access to the links between Jira issues and remote objects in remote applications (most typically pages in a Confluence instance).

Velocity example:
#set($remoteIssueLinks = $remoteIssueLinkManager.getRemoteIssueLinksForIssue($issue))
(Since app version 3.4.0.)
$tableLayoutFactory TableLayoutFactory helps to construct custom column layouts.
This is useful if you want to have a dynamically changing column layout in your PDF documents, identical with the one you currently use see in the Issue Navigator.
$thumbnailManager ThumbnailManager can return thumbnail versions of image attachments.
This is useful if you want to display the smaller version of image attachments in the PDF document.

Velocity example:
#set($thumbnails = $thumbnailManager.getThumbnails($issue, $user))
$worklogManager WorklogManager gives access to the "logged work" records.
You will use it if you need to process or export work log information.

Velocity example:
#set($worklogs = $worklogManager.getByIssue($issue))
$workRatio WorkRatio calculates Jira's special "work ratio" metric.

Velocity example:
#set($percentage = $workRatio.getWorkRatio($issue))
(Since app version 3.3.0.)
$date DateTool is the tool for free date and time formatting.
It should only be used in case of very specific formatting requirements. Otherwise see $userDateTimeFormatter.

Velocity example:
$date.format("yyyy-'W'ww-EEE", $currentDate)
(Since app version 3.3.0.)
$dateFormatter DateTimeFormatter is Jira's built-in date formatter.
There is a preferred shorthand available: see $userDateTimeFormatter.

Velocity example:
$dateFormatter.forLoggedInUser().withStyle($dateTimeStyle.RELATIVE).format($currentDate)
(Since app version 3.3.0.)
$include Helps to include one template in another.
It promotes the good practice of externalizing the reusable parts of your templates, and use those as "include snippets" in multiple concrete templates.

Velocity example:
$include.parse($ctx, "your-company-header-fo.vm")
$math MathTool is to perform basic floating point arithmetic operations in Velocity.
In case you do lots of calculations, we strongly suggest implementing those in Groovy. Velocity is primarily a template language, thus it is not the best fit for this purpose.

Velocity example:
#set($totalTimeSpent = $math.add($totalTimeSpent, $worklog.timeSpent))
$number NumberTool is to format Number objects as integer, floating point, percent or currency.

Velocity example:
$number.format("integer", $storyPoints)
$stringutils StringUtils offers a lot of useful utility methods for working with strings.

Velocity example:
#set($headingLevel = $stringutils.countMatches($issue.summary, "."))
$gadget Exports Jira dashboards, gadgets and reports to PDF.
See the dashboard exporting tutorial for details.
$pdfContent Get the list of comment objects:
#set($comments = $pdfContent.commentsByIssue($issue))

Get the list of change history objects:
#set($changeHistories = $pdfContent.changeHistoriesByIssue($issue))
$pdfFormatter To pretty print file sizes:
$pdfFormatter.formatFileSize($attachment.filesize)
$pdfRenderer Renders the text type fields that rely on Jira's wiki style renderer in a way that they preserve their formatting (bold, italic, lists, tables, images, etc.) also in the resulted PDF document!

Velocity example:
$pdfRenderer.asRendered($issue, 'description', $issue.description)
$scripting Executes Groovy scripts to implement advanced logic and visualization (ex: charts) in templates.
Make sure you read about executing Groovy scripts.

Velocity example:
$scripting.execute("hello-world.groovy")
$sorter SortTool is to sort a collection by any property (or even properties) of the objects contained by the collection.
This can be useful for simple use cases. For complicated ones, we suggest using powerful Groovy comparators.

Velocity example:
#foreach($issue in $sorter.sort($issues, "key"))
	<fo:block>$xmlutils.escape($issue.summary)</fo:block>
#end
(Since app version 5.9.0.)
$userDateTimeFormatter This DateTimeFormatter instance, using the currently signed-in user's preferences, should be the primary tool for date and time formatting.
If you have custom requirements that cannot be met with this, see $date.

Velocity example:
$userDateTimeFormatter.withStyle($dateTimeStyle.COMPLETE).format($currentDate)
(Since app version 5.9.0.)

Expressions for templates

These are the best resources to find the template language expressions for your own templates:

  1. The Expressions Reference Manual gives you categorized expressions for all the frequent needs, that you can just copy to your own templates.
  2. The code of the default templates shipped with the app are also worth a deeper look. We offer several templates built for real-life use cases, so make sure to check the default templates that export the same data or work similar to your own templates.
  3. To better understand the data model and the possibilities, study the Jira domain model documentation. You will learn which class offers what properties and what methods, and their general responsibilities.

Encoding in international templates

You are writing your template text directly to the fo.vm files which are actually plain text documents stored in the file system. You might encounter hard to track encoding problems, because your text editor and your file system must encode the text files properly.

To prevent these potential problems, there is an easy trick: since the fo.vm files are XML files, you can encode your template text characters into NCRs. With this, encoding doesn't matter anymore!

Using this web based converter tool you can:

  1. paste your template text to the Characters: box
  2. click Convert above Characters:
  3. copy the converted string from the Hexadecimal NCRs: box to your text editor. (These are just hex characters, so there will be no problem.)

Embedding images and issue attachments in the PDFs

Embedding external images

In short: images are fully supported. You will use the <fo:external-graphic> element, which supports every sort of resizing options.

This code snippet will give you an idea, or will even do the job for you in most of the cases:

<fo:external-graphic width="100%" height="100%" content-width="scale-to-fit" content-height="100%" src="url('http://www.midori-global.com/images/midori_logo.gif')"/>

For more information about the parameters, please see the tons of great tutorials available on the web.

Embedding images attached to issues

The default issue-fo.vm template differentiates between image attachments and non-image attachments. For the former, it displays the thumbnails. For the latter, it simply prints the filenames and file sizes.

In certain applications, you might rather want the image thumbnails not to be shown in the PDF. This is easy: just set the $showThumbnails option to false in the template, and restart Jira.

Embedding all types of files attached to issues

In some cases, you may want to embed all the files attached to the exported issues in the PDF, not only the images.

This is also supported! It makes your PDFs real self-containing units of information. Please read the embedding issue attachments page for details.

Implementing custom logic with Groovy scripting

Some document types need more logic than just simple if-then statements. You may want to, for instance:

  • Draw charts. (Ex: generate graphical visualization in project status reports.)
  • Integrate with external resources. (Ex: integrate vendor information into your quotes queried from an external CRM database or an external webservice.)
  • Do precise arithmetic. (Ex: calculate precise money values in invoice documents.)
  • Access Jira internals. (Ex: execute a secondary saved filter to collect more data.)
  • Implement data processing algorithms using advanced data structures. (Ex: build dependency tables for traceability matrixes.)

To implement these you can easily write Groovy scripts and execute those scripts while generating the final PDF document! Please read the scripting to implement custom logic page.

Rendering charts

Charts are visual representations of Jira data. Charts often make it easier to understand the data in business reports, because readers can easily pick out patterns and trends illustrated in the charts that are otherwise difficult to see when looking at raw numbers.

A detailed, yet easy to follow tutorial that will explains how to collect data for charts, how to customize the look of charts and how to insert them to the final PDF documents is available here.

PDF document properties

The app generates a sensible default title and default filename for each output PDF document. You can optionally override one or both of those via the template. This offers an easy way to generate consistent document properties.

How is the title used? Well, it is completely up to the template. Most typically, the title is displayed in the top part, in the header, in some cover page, or the template may not use it at all.

How is the filename used? It will be offered as the default filename when downloading and saving the PDF document from the browser. Therefore, properly defining the filename via the template can eliminate the need for tedious file renames.

Default title

The default title is generated according to the following logic:

  • "issue key" for single issues (e.g. "FOO-123")
  • "search request name" for saved filters with a name (e.g. "Team Alpha Pending Tickets")
  • "PDF view name (Jira title)" for ad-hoc searches without a name (e.g. "Worklog Report (ACME Jira)")

For dashboard exports:

  • "dashboard name" for dashboards with a name (e.g. "Service Overview")
  • "PDF view name (Jira title)" for dashboards without a valid name (should never happen)

Customizing titles

To override the title for the generated PDF document, just assign a string to the context variable title any time during the rendering process. Please note that this variable originally contains the default title, allowing you to set the final title based on the default one. (Please see the scripting tutorial for executing scripts and defining context variables.)

You can set it in two ways:

  1. In the Velocity template:

    #set($title = "${project.name} ${version.name} Release Notes")

    This will set the title to "MongoDB 1.2.0-beta Release Notes", for example.

  2. Alternatively, you can also do this in Groovy:

    // "title" is a standard Java String, not a GString
    title = "North American Revenues - ${new Date().format('yyyy MMM')}".toString()
    

    This will set the title to "North American Revenues - 2016 Nov", for example.

Default filename

The default filename is simply the title plus the ".pdf" file extension, e.g. "FOO-123.pdf" or "Team Alpha Pending Tickets.pdf".

Customizing filenames

Similarly to the title, to override the filename, just assign a string to the context variable filename. You can do this any time during the rendering process.

You can set it in two ways:

  1. In the Velocity template:

    #set($filename = "${project.name}-${version.name}-release-notes.pdf")

    This will result in "MongoDB-1.2.0-beta-release-notes.pdf", for example.

  2. Alternatively, you can also do this in Groovy:

    // "filename" is a standard Java String, not a GString
    filename = "north-american-revenues-${new Date().format('yyyy-MM')}.pdf".toString()
    

    This will result in "north-american-revenues-2012-11.pdf", for example.

Customizing metadata

Better PDF Exporter for Jira enables you to set PDF/A metadata for your PDF documents.

Metadata is information about the document itself, like its author, original title and abstract. This is particularly useful when storing these files in a Document Management system, indexing them in some search application or using them in process automation.

You can set the metadata in your template simply:

<dc:title>$xmlutils.escape($title)</dc:title>
<dc:creator>$xmlutils.escape($user.displayName)</dc:creator>
<dc:description>Issues exported from Jira</dc:description>

As the definition of metadata is a core part of your document template, you allowed to do anything to configure the metadata values that you could to customize real content.

You can check the resulted metadata in Adobe Reader by opening the "Document Properties" dialog:

Configuring paper size and DPI

The FO → PDF conversion settings (like paper size, DPI, font paths and base URLs for relative URLs, etc.) can be configured in the fop-config.xml configuration file. See more information about the options in the original FOP documentation.

Specifying paper size right in the templates

In case you prefer configuring paper size locally in the templates, you can do that trough the attributes of the <simple-page-master> element:

<fo:layout-master-set>
	<fo:simple-page-master master-name="A6-landscape" page-height="10.5cm" page-width="14.8cm" margin=".6cm">
		<fo:region-body region-name="page-body"/>
	</fo:simple-page-master>
</fo:layout-master-set>

Auto-selecting templates

Auto-selecting templates by project

Say, you have two templates issue-fo-foo.vm and issue-fo-bar.vm. You want to define a single PDF view "My PDF export" which should render issue-fo-foo.vm in the "TEST" project and issue-fo-bar.vm in all other projects. In other words, the PDF view should intelligently select the template based on the project of the first passed issue.

Steps:

  1. Use the following dispatcher code in the main issue-fo.vm. This reads the project key from the first passed issue and dispatches to different templates based on the project key:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    
    ## dispatch to a template based on the project key
    #set($projectKey = $issues.get(0).project.key)
    #if($projectKey == 'TEST')
    	$include.parse($ctx, "issue-fo-foo.vm")
    #else
    	$include.parse($ctx, "issue-fo-bar.vm")
    #end
  2. Create the two actual templates issue-fo-foo.vm and issue-fo-bar.vm through the Template Manager.
  3. Remove the first line in each of the actual templates. Delete this in both issue-fo-foo.vm and issue-fo-bar.vm:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    If you forget this, then this declaration will be there both in the first line of the dispatcher template (which is fine) and in the first lines of the actual templates (which is not allowed and will raise an XML parsing error).

Using this technique, you can select different templates based on the view mode (single issue or search request), on the number of issues (single or multiple), on field values, or on any other condition that can be evaluated in the rendering context.

Auto-selecting templates by issue type

As another example, here is the code that selects the template based on type of the issues:

<?xml version="1.0" encoding="ISO-8859-1"?>

## dispatch to a template based on the issue type identifier
#set($issueTypeId = $issues.get(0).issueTypeObject.id)
#if($issueTypeId == '6')
	$include.parse($ctx, "issue-fo-foo.vm")
#else
	$include.parse($ctx, "issue-fo-bar.vm")
#end

Next step

Run short Groovy scripts to integrate data from external resources (external database or API), to access Jira internals (ex: getting project or version metadata) or to pre-process data before exporting that to PDF.

Questions?

Ask us any time.