Sam Dey1
Kilo Guru

Recently, working for a client the requirement was really interesting. They have a 4-page form for casual hire. The first 3 pages are to be filled by a nominator and the last one by the nominee. So essentially I needed to make a 3-page pdf form into a Catalogue Item, and for the fourth part, when pdf goes to the nomine, they need to check the first 3-part form as filled up by the nominator.

I came up with the solution that, if the nominee is an existing employee then he/she can access the request item with the link of the request item, but the issue is with the nominee being external entity. In that case, I suggested that the notification to the nominee contains a pdf file that will capture everything that is filled by the nominator in the catalogue item.  

Auto filling PDF with catalogue item data is a bit tricky one and there isn’t much documentation about it so I am sharing my idea and methods that I implemented for this.

There are two ways of doing it:

1st method is the “.write” function.

Syntax: attachment.write('tablename', recordsysid, fileName, content_type, fileDate);

“attachment” is the object variable of Attachment() type

“tablename” is string variable that contains the name of the table where you want to attach the file.

“recordsysid” is the sys id of the record where you want to attach the file

“filename” is the file name of the attachment

“content_type” is the type of content in the file (usually for pdf it is ‘text/text’)

“fileData” contains the data that you want to put in the file.

Example: Business Rule to run after insert

var tableName = "sc_req_item"; //Request Item Table

var recordsysid = current.sys_id; //sys id of the record

var fileName = current.number+" FormData.pdf";

var content_type = 'text/pdf';

 

var fileData=""; // Declare an empty string

 

fileData+=current.number+" "+current.short_description+"</ b>";           // current access the varibles in the record

fileData+=current.variables.u_requested_for.getDisplayValue()+"</ b>";    // current.variables access the variables or fields in the form or catalog Item

fileData+=current.variables.u_department.getDisplayValue();

 

var attachment = new Attachment();

attachment.write('tablename', recordsysid, fileName, content_type, fileDate);

 

While this method is good but it won’t support any formatting of the pdf layout. So you get a basic pdf with the data and you cannot put heading and other formatting details. Hence the 2nd method.

 

2nd Method is using the global.GeneralPdfUtils() of SNOW.

Syntax: pdf.prefillPdf(jsonString, destinationTableSysId, attachmentSysId, destinationTableName, pdfName);

“pdf” is an instance object of global.GeneralPdfUtils()

“jsonString” contains the data that you gonna insert into the pdf in JSON format (it needs to be in JSON for a reason)

“destinationTableSysId” sys id of the record where you are attaching the file

“attachmentSysId” sys id of the pdf template that you need to upload first, in which the data will be filled

“destinationTableName” table name of the record where you are attaching the file

“pdfName” naming the pdf file to be attached

 

Example: Business Rule to run after insert

var attachmentSysId="56d921c4db3a9f806223147a3a9619ea";

var pdf = new global.GeneralPdfUtils();

var jsonObject = {

                                           "title_category":current.variables.u_title_category.getDisplayValue(),

                                       "name_nominator":current.variables.u_requested_for.getDisplayValue(),

"school_nominator":current.variables.u_school_centre_section.getDisplayValue(),

                                      "mail_nominator":current.variables.u_mailing_address.getDisplayValue(),

                                      "email_nominator":current.variables.u_email_address.getDisplayValue()}

var jsonString = JSON.stringify(jsonObject);

var destinationTableName = 'sc_req_item';

var destinationTableSysId = current.sys_id;

var pdfName = current.number+" Application Details.pdf";

pdf.prefillPdf(jsonString, destinationTableSysId, attachmentSysId, destinationTableName, pdfName);

 

TWO IMPORTANT THINGS ABOUT THIS;

The reason why we need a JSON object is because we are actually mapping the data. So first thing, you need to create a PDF template, I created a word file (because I find it easier to type heading and all in word) and then converted it into a pdf. Opened the file in Adobe Acrobat and opted for the “Forms>>Add Edit Fields”. This let you create fields in the pdf. I used checkboxes and text input fields. Once you create a field, name the field, and use the same name as object propertyName.

For instance, form the above example, I named a field “title_category” in the pdf and so the JSON object has a propertyname “title_category” and then you assign the value of this specific property as “current.variables.u_title_category.getDisplayValue()” so this will fetch the data from the form field named ‘u_title_category’.

 

 

And that should work just fine.

I am attachingsample files (Output PDF, PDF Template and sample code)

Hope it helps someone 😊

Comments
Chris L5
Tera Contributor

Thank you for this article.   

I am starting out in Service Now Development and needed a way to create PDFs and Fill them in using data from Tables.

Your article is helpful as long as I have an existing Fillable PDF.  

Unfortunately, I am struggling with finding a solution that allows me to build a PDF from scratch.

At the moment, I don't have access to Adobe Licensed software to author PDFs.

 

I found the PDF Generator plugin, but I am struggling to find a working example of that as well.

One quick question:   How can I find documentation on the "GeneralPdfUtils"  ??

I am not having much luck on that either.  

 

Thanks !

Sam Dey1
Kilo Guru

Hi Chris,

Firstly welcome to the world of SNOW Dev, it’s fun

Sam Dey1
Kilo Guru

Hi Chris,

Firstly welcome to the world of SNOW Dev, it’s fun

Sam Dey1
Kilo Guru

Hi Chris,

Firstly welcome to the world of SNOW Dev, it’s fun.

Okay so first thing first, the GeneralPDFUtils is actually a script include which is part of the PDF Generator plugin, since you have the plugin installed so in your instance, if you open “sys_script_include_list.do” then search for the GeneralPDFUtils you can access the script include. There is no documentation as such, it is moderately simple so you can explore it by yourself, if you have any question regarding the script, you can put it here and I will try my best to explain whatever I can.

 

Now, “I am struggling with finding a solution that allows me to build a PDF from scratch.” – for this bit, if you check the first method (using the “. write” function) in the article you will get the solution.

So, just to reiterate, the first solution is that you create a file in pdf from scratch with the data from the table record through a Business Rule, the drawback is that it is fairly simple one without any header/footer or any specific formatting.

 

Let me know if you need any more info/help. Happy coding

 

Regards,

Sam

 

Chris L5
Tera Contributor

Thank you for such a quick response !  Your comments were very helpful.  I am still digging to learn more.   

I was trying out the first section of your article.. whereby you create a PDF (but won't have access to headers/footers). My code is throwing an exception when I get to the Attachment()  line. Error Message"Attachment" is not defined. 

I was trying to use the code inside a Script Include instead of a Business Rule.  Would that cause a problem ? Of course, at that point, I had to know the record id, etc.  So as a test I was hard coding a known sys_id etc... And then, of course, I had to comment-out most of the lines of the FileData that references 'current' to access the current record. I just hard coded a simple piece of HTML  (or at least I am assuming I am allowed to do that).

Next, I tried to add "global." to the front of Attachment()...  I have seen some articles that indicate sometimes a scope issue might cause problems.. But then I get into a different error:

java.lang.SecurityException: Illegal access to private script include Attachment in scope rhino.global being called from scope x_237223_mypdfgene

I am positive that it's my lack of experience with ServiceNow that is the culprit.  Still trying to fill in the gaps in my knowledge as I encounter issues.  Sorry if this is a N00b-ish question.   

Thanks again for your earlier assistance.  It was very insightful.

Sam Dey1
Kilo Guru

Hi Chris,

Umm so no question is noobish (at-least that’s what I believe), because this question of yours helped me to learn something new (any question always does that, so thank you for this question).

Firstly,

“Attachment()  line. Error Message"Attachment" is not defined. ” –

This line “var attachment = new Attachment();” is basically calling a Script Include called Attachment, which is OOTB in SNOW. So again if you open up “sys_script_include_list.do” in your instance and look for it you will get the script include, but the problem with this one is that by default this is accessible from within the scope where it is defined which in this case is Global. So basically, if you write your attachment related script include in a Global application then only you can access this OOTB Script include.

So again the reason why sometimes we mention “global” (or any application name) in front of any script include is to make sure that the system understands which application to look for the specific script include. In your case you mentioned the new error after writing “global.”  in-front of “Attachment()” – ‘Illegal access to private script include Attachment in scope rhino.global being called from scope x_237223_mypdfgene’ so  this is simply saying you that you cannot access the script include Attachment defined in Global scope from your scope x_237223_mypdfgene.

The solution is you can update the OOTB Attachment Script include to be Accessible from “All Application Scope”.

Now, most importantly – while going through your question, I initially tried to run my script and find out some issues with it. So this script – the first part is working when you want to create a text file and NOT A PDF file. This might be for new versions of SNOW, because the second part is still working. But obviously you are not interested in the second part. So, after few searches I got another solution for you -

 

var source_tableName = "sc_req_item";

var source_recordSYSID = "3194244637a32300180edcc773990e59" ;// sys id of the record's data you want to use in the pdf

var source_view  = "ess"; // View name in this case I am using the Self Service View

var target_tableName = "sc_req_item"; // In this case target and source is same, it could be different as well

var target_recordSYSID = "3194244637a32300180edcc773990e59"; // In this case target and source is same, it could be different as well

var pdfName = "Test.pdf"; // Could be anything but do mention the extension .pdf

var basicAuthenticatorSYSID = "000000007a32300180edcc773990e39"; // this is the sys id of the Basic Authentication Profile which is available from sys_auth_profile_basic_list.do table.

 

generatePDFUsingREST(source_tableName,source_recordSYSID,source_view,target_tableName,target_recordSYSID,pdfName,basicAuthenticatorSYSID);

 

/*

Calling this function generatePDFUsingREST creates a REST message and collects the data defined in the URL endpoint and then saveResponseBodyAsAttachment function (which is OOTB) creates an attachment in the parameter table

*/

 

function generatePDFUsingREST( sourceTable, sourceID, sourceView, targetTable, targetID, fileName, authProfileSysID ) {

 

    var r = new sn_ws.RESTMessageV2();

    r.setHttpMethod('get');

    r.setEndpoint(gs.getProperty('glide.servlet.uri')+ sourceTable + '.do?PDF&sys_id=' + sourceID + '&sysparm_view=' + sourceView);

    r.setAuthenticationProfile('basic', authProfileSysID);

    r.saveResponseBodyAsAttachment(targetTable, targetID, fileName);            

    var response = r.execute();   

 

}

 

Any other question please let me know.

Cheers,

Sam

Chris L5
Tera Contributor

Ah!  ok.. that is making a lot of confusion go away for me.   I had come across an article about the REST approach to generating PDF, but I didn't realize that it can be used to create PDF as an attachment.  Your response is quite enlightening when you mentioned that I can  switch the accessible option to "All Application Scope”.   I wouldn't have expected that to be something I could edit.  I figured it was read-only.  Paying closer attention to the dialog.. I did see the "edit" link at the top of those script forms/page/view (whatever the term).  And sure enough it let me change the accessibility scope.  Nice !

 

I will give your suggestions a try.

Thank you again for such a quick and information response !

 

 

Chris L5
Tera Contributor

Ah!  ok.. that is making a lot of confusion go away for me.   I had come across an article about the REST approach to generating PDF, but I didn't realize that it can be used to create PDF as an attachment.  Your response is quite enlightening when you mentioned that I can  switch the accessible option to "All Application Scope”.   I wouldn't have expected that to be something I could edit.  I figured it was read-only.  Paying closer attention to the dialog.. I did see the "edit" link at the top of those script forms/page/view (whatever the term).  And sure enough it let me change the accessibility scope.  Nice !

 

I will give your suggestions a try.

Thank you again for such a quick and informative response !

Chris L5
Tera Contributor

First Question:  Is this REST Request considered an Outbound Request since it's using the V2 API and basic auth ?

2nd Question:  We are making a REST request to our same instance, correct ?

3rd Question:  What is the difference between a Scripted REST API and this REST Request we are attempting to use.  Im assuming they are the same thing, except that perhaps a "Scripted REST API" is something custom we write ourselves whereas anything that is built into ServiceNOW is called something else ? (maybe ?)

I went ahead and setup a UI-Page, with a simple button,  that has jQuery event handler to call a Server Side Include.  Then the Server-Side Include is calling that function "generatePDFUsingREST()".   And that function is defined in the body of the Script Include as well.  Good so far ? (or am I still making things more difficult for myself than needed ?)

 

Sam Dey1
Kilo Guru

Okay great work, play around and you will end up exploring more than reading or studying.

So for the first question – When you are calling the function (from my example script) “generatePDFUsingREST()” this contains the REST message right.

So,

“var r = new sn_ws.RESTMessageV2();” – this initiates a REST message and its outbound one.

“r.setHttpMethod('get');”  - this sets the method of the REST as ‘get’

“r.setEndpoint(gs.getProperty('glide.servlet.uri')+ sourceTable + '.do?PDF&sys_id=' + sourceID + '&sysparm_view=' + sourceView);” – this sets the endpoint, now interestingly since your url is the same instance from where you are initiating the REST so for this part this is an Inbound REST.

 

Another point, so when you are using an UI Page you don’t specifically need to call a Script Include the reason being, UI Page have 3 scripting part right – HTML, Client Script and Processing Script – this part (Processing Script) is for server side scripting. So you can use it as well.

 

Now for the Basic Authentication part – this is bit confusing, you don’t need to use OAuth for REST V2, you can simply use Basic Authentication, so what you need to do is need to create a Basic Authentication profile with a username and password of an existing user’s username and password. So what is happening there is on a broad perspective, system looks for the Basic Authentication profile sys id in the REST script and goes to that Basic Auth profile, where it finds the username and password of an existing user. Also, it is helpful to use a user’s username and password for the basic auth who has got admin role. This is just to ensure that any access control will not stop the process or the REST message returning with less info for access issues.

 

Hope it helps, shoot me any question if you get stuck. 
Cheers,

Sam

Sam Dey1
Kilo Guru

3rd ans – so if you look for REST Api Explorer you will see that there are many pre-defined REST Messages in the instance which are OOTB. And yeah you are right, you can define Scripted REST API which contains scripting element as well. So for instance, when you use that specific REST Message then you get a specific response and you can define what exactly will be returned if that REST message is used.

Chris L5
Tera Contributor

Thanks for the confirmation about the whole inbound/outbound concept. I keep finding ServiceNow Articles that, at least from my understanding / take-away, make me believe that the concept of inbound and outbound rest services are two separate things.  The steps and areas where configuration happen for these 2 concepts seem to always be completely separate.  Wasn't sure if I was supposed to follow steps for both or not.  Good to know. 

Also, thanks for the info about the Processing Script section of the UI-Page. Somehow, even with the combined sources of the online course I bought, and the reading I have done so far.. I didn't know exactly how that "Processing" section was supposed to be used.  I got the wrong impression that it was only for Validating a Form prior to actually POSTing it to the Server.   Nice to know I can use it for server side scripting (at least for things specific to this UI-Page.)

Chris L5
Tera Contributor

So in addition to the source code that you provided...  I got to learn how to Properly setup and configure "Basic Authorization Profile." It works with Basic Auth just like you said. 

I had to create a User, assign Roles for 'admin', 'itom admin', 'itil admin', and 'webservice admin'.  (maybe not ALL of them are required, will try to pair down the roles to absolute bare minimum.. but was rushed.)    Then I had to make sure that my username and password for this new User was correct in both the User Table AND in the Basic Auth Profile.   

Then I also got to learn about the "REST Message" configuration which requires the "Basic Auth" Profile. (or OAuth if you choose to go that route.)  Once the User, the Basic Auth Profile, and the REST Message were all configured and hooked up together..  things 'just worked'.   

I was also able to avoid using the Script-Include and just put the server-side code in the "Processing Script" section of my UI-Page.  Granted, I could have also just replaced the entire UI-Page with a Business Rule..  but I didn't have a workflow that would generate a record which would trigger the Business Rule.  So I went with something visual that also gave me easy ability to see debug messages via the 'AddInfoMessage()" calls.

 

Thanks again for your patience and willingness to point me in the right direction 🙂

Sam Dey1
Kilo Guru

Awesome, happy coding 

🙂

kemmy1
Tera Guru

So reading all of this information, I created a template pdf and an after business rule to run when a change is inserted:

var attachmentSysId = "0c783094dbcf33803a8034cc7c9619de";
    
    var jsonObject = {
        "pdf_name": "Sam Dey",
        "day": "Yes",
        "chk_box": "Yes",
        "Text1": current.short_description.getDisplayValue()
    }
    
    var pdf = new global.GeneralPdfUtils();
    var jsonString = JSON.stringify(jsonObject);    
    var destinationTableName = 'change_request';    
    var destinationTableSysId = current.sys_id;    
    var pdfName = "Test Pdf Details.pdf";    
    pdf.prefillPdf(jsonString, destinationTableSysId, attachmentSysId, destinationTableName, pdfName);

 

Works like a charm BUT......

What I really need is a way to create a pdf and attach it to another table/record.  Right now I created a script include that includes this:

var r = new sn_ws.RESTMessageV2();  

        r.setHttpMethod('get');
        r.setEndpoint(gs.getProperty('glide.servlet.uri') + tblName + '.do?PDF&landscape=true&sys_id=' + objSysId + '&sysparm_view=' + 'Default');
        r.setBasicAuth('pdfuser','password');
        r.saveResponseBodyAsAttachment('u_dhs_meeting_package', rec, fileName);      
        var response = r.execute();
    },

 

But of course all it's doing is creating a pdf as if you are exporting a pdf from the change record (no formatting).  Is there any way to be able to incorporate the jsonObject script into some how creating a pdf that is using data from a particular change request record and adding it to the u_dhs_meeting_package record.

 

 

Lisa

Sam Dey1
Kilo Guru

Hey Lisa,

So first thing is you can actually save the pdf record from one table to another.

In that case just change the following - 

var target_tableName = "u_dhs_meeting_package"

var target_recordSYSID = <sys_id of the record where you want to save the pdf>

Rest of the things will remain same and that should work fine. 

 

For the second part of the question - 

So the response we get with the above script is really gibberish to work on. 

So we cant mix the two methods from my understanding. 

 

Also if you want a specific data from the source record then create a view in the source record where you add all the fields which you want for the pdf and then use that view in the endpoint line.

For instance, if i create a view called myView then the endpoint line would be 

r.setEndpoint(gs.getProperty('glide.servlet.uri') + tblName + '.do?PDF&landscape=true&sys_id=' + objSysId + '&sysparm_view=' + 'myView');

 

Hope this helps,

let me know if you have any doubt or question. 

 

Cheers,

Sam

Chris L5
Tera Contributor

There are examples that show how you can attach a file to another record.

In my case... I had to create a PDF... and then attach it to a Service Request Record.

I will need to dig thru my notes and find the example.

Will post it when I can find it.

 

Amit117
Kilo Explorer

Hi Sam,

Thank you for your article.

I have one query while using 2nd method, Is there a way we can populate fillable PDF from Embedded list available on form ?

Please guide regarding it.

Thanks,

Amit

Sam Dey1
Kilo Guru

Hey Amit, 

Sorry for the late reply. Actually no there is not a direct way but you can always GlideRecord to get the data you want 

 

Jay Michael
Tera Expert

Hi @Sam Dey - thank you for providing this resource. I am trying to generate a PDF from a template without the use of HRSD. 

I had a question about method #2:

'“attachmentSysId” sys id of the pdf template that you need to upload first, in which the data will be filled'

What table is this attachment pdf template record supposed to be on?

Sam Dey1
Kilo Guru

Hi @Jay Michael 

thanks for reaching out. 

"What table is this attachment pdf template record supposed to be on?" - It could be in any table on any record. All you need is the attachment sys_id. So for me, I used an dummy knowledge article record, and made the short description "DO NOT DELETE EVER" 🙂 so that nobody deletes it and it was unpublished so it was inaccessible. All you need is the attachment sys_id, irrespective of table or record type it is in. 

Hope this helps, please feel free to reach out if you have any other question. 

Cheers,

Sam

Jay Michael
Tera Expert

Thank you Sam!! This clarified things for me and method#2 is working perfectly. Thank you for providing this documentation!

Best

harshada2
Tera Contributor

I am getting an error when i try to use write method

Nalinisundari
Tera Contributor

Hi,


Please help to resolve the below mention the details...

They will be auto populated and generate form will send to one to another module.

 

Screen Shot is needed

 

Nalinisundari_0-1678251140947.png

Please to do needful 

Ashley
Kilo Sage

Amazing article, solution 2 worked for me first time.

 

Thanks for sharing,

 

Kind Regards

Ashley

Version history
Last update:
‎06-14-2018 06:08 PM
Updated by: