Callum Ridley1
Tera Guru

ServiceNow recently released a new plugin for the platform (Paris onwards) to enable better support for custom PDF generation using script. Unfortunately the only documentation on this new feature set you will find is in KB0866613, which does not go very far in explaining how to use the API. I'm sure ServiceNow will eventually document this, or even build some full featured platform functionality around it, but for now I hope this article will get you started.

The new API appears to be based on iText7, which is a major improvement over the old GeneralPDF that was based on iText5. The newer version of iText is much better at handling HTML conversion, and once you start to explore the feature set, there is quite a lot you can do with just plain HTML and CSS.

API Overview

At the time of writing this article, there are two known methods available on the API.

convertToPDFWithHeaderFooter

ParameterTypeDescription
htmlStringThe HTML content to convert to a PDF
targetTableStringThe table name to attach the generated PDF to
targetSysIdStringsys_id of the record to attach the generated PDF to
filenameStringThe file to to save the generated PDF as
pagePropertiesObjectJavascript object with properties used during PDF generation
fontSysIdStringsys_id of a font family record on the 'sys_pdf_generation_font_family' table

pageProperties

propertyTypeDescription
HeaderImageAttachmentIdStringsys_id of an attachment record
HeaderImageAlignmentStringOne of: 'LEFT', 'CENTER', 'RIGHT'
PageSizeStringOne of: 'LEGAL', 'LETTER', 'A4'
GeneratePageNumberStringOne of: 'true', 'false'
TopOrBottomMarginStringNumber as a string e.g. '72'
LeftOrRightMarginStringNumber as a string e.g. '36'

Example

var pageProperties = {
	HeaderImageAttachmentId: 'attachment_sys_id',
	HeaderImageAlignment: 'LEFT',
	PageSize: 'A4',
	GeneratePageNumber: 'true',
	TopOrBottomMargin: '72',
	LeftOrRightMargin: '36'
};

var html = '<h1>Hello World</h1>';

new sn_pdfgeneratorutils.PDFGenerationAPI().convertToPDFWithHeaderFooter(html, 'incident', 'incident_sys_id', 'My First PDF', pageProperties, 'font_family_sys_id');

convertToPDF

ParameterTypeDescription
htmlStringThe HTML content to convert to a PDF
targetTableStringThe table name to attach the generated PDF to
targetSysIdStringsys_id of the record to attach the generated PDF to
filenameStringThe file to to save the generated PDF as
fontSysIdStringsys_id of a font family record on the 'sys_pdf_generation_font_family' table

Example

var html = '<h1>Hello World</h1>';

new sn_pdfgeneratorutils.PDFGenerationAPI().convertToPDF(html, 'incident', 'incident_sys_id', 'My First PDF', 'font_family_sys_id');

On the face of it, it would appear as though the convertToPDFWithHeaderFooter is the more flexible of the two methods, however as you'll see below, we can actually control the entire document ourselves with HTML and CSS and just use the convertToPDF method.

CSS 3 Paged Media

The CSS 3 specification sets out functionality for paged media, for example printing. When it comes to PDF we can use the paged media rules to define how the document should be generated using the ServiceNow PDFGenerationAPI / iText7.

@page rule

The @page rule allows us to specify details about the pages we will be generating, including the margins, size, orientation, page numbering rules etc. 

Example

<style>
   @page {
      size: A4 landscape;
      margin-left: 1cm;
      margin-top: 3cm;
      margin-right: 1cm;
      margin-bottom: 3cm;

      @bottom-right {
        font-family: sans-serif;
        font-weight: bold;
        font-size: 1em;
        content: counter(page);
      }
   }
</style>
<h1>
   Hello World!
</h1>

Resulting PDF

 find_real_file.png

 

Headers and Footers

The creation of headers and footers is equally as easy. This uses a feature called 'Running elements'. At first they may not make much sense in how you define them, but see the below for an example.

<style>
   @page {
      size: A4 landscape;
      margin-left: 1cm;
      margin-top: 3cm;
      margin-right: 1cm;
      margin-bottom: 3cm;

      @top-center {
        font-family: sans-serif;
        font-weight: bold;
        font-size: 1em;
        content: element(runningPageHeader);
      }
   }
   
   #pageHeader {
      position: running(runningPageHeader)
   }
</style>

<div id="pageHeader">
   My Header!
</div>

<h1>
   Hello World!
</h1>

Resulting PDF

find_real_file.png

How this works

The div element with the id 'pageHeader' would normally be rendered in the position its included in the code on the page, however the style block of the code contains a CSS rule for the element #pageHeader, specifying position: running(runningPageHeader). This removes the element from the rendered page and adds it into a css variable, in our case the variable is called 'runningPageHeader'. Once the content is in the variable, we can use that same variable name to include it as content in the @top-center rule using content: element(runningPageHeader).

This can be a difficult concept to get your head around at first, and it can get more complicated when you want to include a page number in your custom HTML header content too. For example:

<style>
   @page {
      size: A4 landscape;
      margin-left: 1cm;
      margin-top: 3cm;
      margin-right: 1cm;
      margin-bottom: 3cm;

      @top-center {
        font-family: sans-serif;
        font-weight: bold;
        font-size: 1em;
        content: element(runningPageHeader);
      }
   }
   
   #pageHeader {
      position: running(runningPageHeader)
   }
   
   #currentPageNumber:before {
      content: counter(page)
   }
</style>

<div id="pageHeader">
   My Header! <span id="currentPageNumber"/>
</div>

<h1>
   Hello World!
</h1>

Resulting PDF

find_real_file.png

 

Images

Images are also possible with relative ease. You can use the usual img elements in your html, and even use absolute URLs to image content, be aware though that if the image is hosted on your ServiceNow instance, you need to make sure the image is publicly accessible. Attachments against records won't work unfortunately as the PDF generation API is not authenticated to your instance.

Example

<style>
   @page {
      size: A4 landscape;
      margin-left: 1cm;
      margin-top: 3cm;
      margin-right: 1cm;
      margin-bottom: 3cm;

      @top-center {
        font-family: sans-serif;
        font-weight: bold;
        font-size: 1em;
        content: element(runningPageHeader);
      }
   }
   
   #pageHeader {
      position: running(runningPageHeader)
   }
   
   #currentPageNumber:before {
      content: counter(page)
   }
</style>

<div id="pageHeader">
   My Header! <span id="currentPageNumber"/>
</div>

<h1>
   Hello World!
</h1>
<img src="URL_TO_IMAGE" width="100px"/>

Resulting PDF

find_real_file.png

 

Fonts

It is possible to add and use custom fonts into the PDFs that you generate. I've only tried this with TTF (TrueType) fonts, though it may work with other formats too. It's possible to add more than one font to a PDF, see the following example.

  1. Create a new Font Family [sys_pdf_generation_font_family] record
  2. fill in the name field with whatever you like, it doesn't make any difference to the process.
  3. Save the record
  4. Attach your font files to the record.

find_real_file.png

When using the API, the last parameter of both methods is the font family sys_id, you can only supply one sys_id to the method, which is why you add multiple fonts to the same Font Family record.

To use the font in your document, you simply specify the font-family CSS attribute in your CSS

Example

<style>
   @page {
      size: A4 landscape;
      margin-left: 1cm;
      margin-top: 3cm;
      margin-right: 1cm;
      margin-bottom: 3cm;

      @top-center {
        font-family: sans-serif;
        font-weight: bold;
        font-size: 1em;
        content: element(runningPageHeader);
      }
   }
   
   #pageHeader {
      position: running(runningPageHeader)
   }
   
   #currentPageNumber:before {
      content: counter(page)
   }
   
   .angry-birds-font {
      font-family: AngryBirds;
   }
   
   .flow-font {
      font-family: Flow;
   }
</style>

<div id="pageHeader">
   My Header! <span id="currentPageNumber"/>
</div>

<h1 class="flow-font">
   Hello World!
</h1>
<h1 class="angry-birds-font">
   Angry Birds
</h1>

Resulting PDF

find_real_file.png

Summary

Hopefully this article has helped you get to grips with the new PDFGenerationAPI. I have only scratched the surface of the CSS paged media rules, but a little Googling around should get you loads of information on how to use it.

Why not have a play with this on your developer instance and post some screenshots here to show everyone else what you've been able to achieve, I'd love to see some of what you've managed to create.

As always, if this content has helped you, please remember to mark it as helpful so that it can reach as many people as possible.

Callum

Comments
Alexandre17
Tera Expert

Where i "put" the "CSS 3 Paged Media" ?  In the template or in the business rule? 

Jonas VK
Tera Guru

Hey, would it be possible to save the pdf in the sys_attachment table so i could use the url as a download link?

Viji2
Tera Expert

Hello,

I used this API to export the knowledge article that attaches the file to sys_attachment table from where it can be downloaded via a button in portal. I noticed that pdf does not contain images for those articles with 'Display attachments' option set to true whereas the images are included if the option is not enabled. Has anyone faced a similar scenario and found a solution?

oleg7
Tera Contributor

page numbering can be added by using CSS on your page:

 

@page {
  @bottom-left {
    content: counter(page) ' of ' counter(pages);
  }
}

 

Nagashree5
Tera Contributor

Hi @Callum Ridley1 ,

 

Thank you for the article.

Can you please have a look at my requirement.

Requirement : Once a request is submitted through the portal, the form-level variables must be automatically populated in to a customized template and attach to the submitted request.

Suppose the form has three variables - Requested for, Company and Date. Once, these fields are filled and form is submitted, these variables should be populated in a custom pdf like below.

Dear (Requested for),

I'm working for (Company) till (Date)

It should automatically fill these details based on the request and get attached to post submission.

Is this feasible in Servicenow.

Please guide me on this.

Thank you in Advance.

Somen
Tera Contributor

@Callum Ridley1  I wanted to create "Table of Contents" dynamically. And I found "documentConfiguration" which can be used as per the below syntax.

convertToPDFWithHeaderFooter(String html, String targetTable, String targetTableSysId, String pdfName, Object headerFooterInfo, String fontFamilySysId, Object documentConfiguration).

Somen_0-1684322212093.png

But not sure how to use it? Can you explain a bit about it how we can utilize this functionality?

 

Kristen Ankeny
Kilo Sage

Have you run into issues where converting the HTML results in misalignment? We're dealing with that at the moment and I've not yet found a solution. 

suvro
Mega Sage
Mega Sage

How to show this in a UI page or in widget in full screen any idea?

BRUNO BANDEIRA
Tera Guru

This article really saved me! Beautifully done sir! 😁

arleneh
Tera Explorer

Hi,

 

Thanks for this great article.  I have a problem with this method as it won't allow me to set the font. If one of the word uses macron, it changes the default font to Times New Roman.  This is the method I'm having problem with:

 

fillFieldsAndMergeSignature

 

Thanks!

kiran kumar m1
Tera Contributor

Hi @Callum Ridley1   I have created  a UI action for this such that whenever I click on that generate PDF button it will generate the pdf of my form . But in my form I have few HTML type fields in which user can add a image , in my pdf I am only getting the text for those fields , I want the images and the attachments that are associated with the page in this Discussion with Complainant, Discussion with Witnesss are the html fields . Is there any way to show the images present in that field in the pdf ?

 

<p>&nbsp;</p>
<p style="text-align: center;"><span style="text-decoration: underline;"><span style="font-size: 14pt;">SafeSpace Complaint Form</span></span></p>
<table style="border-collapse: collapse; width: 100.849%; height: 197.2px;" border="1">
<tbody>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Case Number</td>
<td style="width: 49.9905%; height: 19.9px;">case_number</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Requested For</td>
<td style="width: 49.9905%; height: 19.9px;">requested_for</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Meeting ID</td>
<td style="width: 49.9905%; height: 19.9px;">meeting_id</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Category</td>
<td style="width: 49.9905%; height: 19.9px;">category</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Subcategory</td>
<td style="width: 49.9905%; height: 19.9px;">subcategory</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Business</td>
<td style="width: 49.9905%; height: 19.9px;">businessunit</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Geo</td>
<td style="width: 49.9905%; height: 19.9px;">geo</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Location</td>
<td style="width: 49.9905%; height: 19.9px;">location</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">Assignment group</td>
<td style="width: 49.9905%; height: 19.9px;">assignment_group</td>
</tr>
<tr style="height: 18.1px;">
<td style="width: 49.9905%; height: 18.1px;">Assigned to</td>
<td style="width: 49.9905%; height: 18.1px;">assigned_to</td>
</tr>
</tbody>
</table>
<table style="border-collapse: collapse; width: 100.7%;" border="1">
<tbody>
<tr>
<td style="width: 49.9905%; height: 19.9px;">Additional assignee</td>
<td style="width: 49.96%;">additional_assignee_list</td>
</tr>
<tr>
<td style="width: 50.0267%;">Short Description</td>
<td style="width: 49.96%;">short_description</td>
</tr>
<tr>
<td style="width: 50.0267%;">Description</td>
<td style="width: 49.96%;">description</td>
</tr>
<tr>
<td style="width: 50.0267%;">Discussion with Complainant</td>
<td style="width: 49.96%;">dwc</td>
</tr>
<tr>
<td style="width: 50.0267%;">Discussion with Respondent</td>
<td style="width: 49.96%;">dwr</td>
</tr>
<tr>
<td style="width: 50.0267%;">Discussion with Witnesses</td>
<td style="width: 49.96%;">dww</td>
</tr>
<tr>
<td style="width: 50.0267%;">Guilty of Charges</td>
<td style="width: 49.96%;">goc</td>
</tr>
<tr style="height: 19.9px;">
<td style="width: 50.0267%;">Recommendation</td>
<td style="width: 49.96%;">recommendation</td>
</tr>
<tr>
<td style="width: 50.0267%;">Close code</td>
<td style="width: 49.96%;">close_code</td>
</tr>
<tr>
<td style="width: 50.0267%;">Close notes</td>
<td style="width: 49.96%;">close_notes</td>
</tr>
</tbody>
</table>
<table style="border-collapse: collapse; width: 100.647%; height: 25.4px;" border="1">
<tbody>
<tr style="height: 19.9px;">
<td style="width: 49.9905%; height: 19.9px;">State</td>
<td style="width: 50.0411%; height: 15.4px;">state</td>
</tr>
<tr>
<td style="width: 50.0411%;">Opened</td>
<td style="width: 50.0411%;">opened_at</td>
</tr>
<tr>
<td style="width: 50.0411%;">Opened by</td>
<td style="width: 50.0411%;">opened_by</td>
</tr>
</tbody>
</table>
<p style="text-align: left;"><span style="font-size: 8pt;">Generated on: generated_date_time</span></p>

Esther12
Tera Contributor

Do you have any information on how to add in data from a multirow variable set?

Mikhail_MA
Tera Contributor

Hello everyone! I've attached different fonts, but only Flow and AngryBirds work as the author showed. I've tested other TTF (TrueType) fonts, but they don't work. Does ServiceNow have specific requirements for fonts?

hamadahmad
Tera Contributor

Hi @Callum Ridley1 ,

Is there any way to generate a pdf with mixed page orientation (e-g, page 11 to be landscape and the rest portrait)? Tried playing around with the page property but no luck. Named pages doesn't work either.

Andy-L
Tera Contributor

Hi, After a few months without any issues, I've just encountered an issue on a production system that bulk converts dynamically generated HTML to PDF. An exception was raised on some of the calls intermittently:

 

"com.snc.services.client.exception.ServerUnavailableException"

With error text "All clients inactive, refresh configuration to reset"

 

So it might be prudent to adjust any automated scripts to allow for this and backoff-retry or similar .

 

Version history
Last update:
‎02-18-2021 02:03 AM
Updated by: