This article is relevant if you are seeking to get a good handle on NetSuite’s Advanced PDF / HTML template technology
Background
I wrote about the general framework about a year ago in this article: “How To: Leverage NetSuite’s Advanced PDF / HTML Generation Tools” . That article offers a good over and references to documents to help you build new templates.
Since that time and as of this writing, NetSuite has announced a roadmap to push customers toward the Big Faceless Organization (BFO) Freemarker framework for generating HTML and PDF documents. The technology is still considered “Beta” but it is clear NetSuite is making a significant investment. In anticipation for a need to master this new technology, I wanted to produce a Freemarker template that can help all of us understand the underlying data that is being passed to us so we can better craft our specific templates used in the business. Meaning, I want to go much deeper into the possibility of using these tools to unlock the power.
Locked Up Data Model
I want to be wrong about this. But after spending about a half day trying everything I could, I have concluded that NetSuite is not supporting an important FreeMarker “Built In” called “keys”. Essentially, when NetSuite provides us with a reference to the template data model such as “record”, we are supposed to know in advance the field name. For example, if you want the date on the record, you would reference “${record.trandate}”.
That is fine and dandy, but it demands much hunt and pecking to get field names. Sometimes, field names are not so obvious especially as you start to traverse NetSuite sublists. I wanted to build a tree representation of all the data being passed to us so we can diagnose the data model and its contents to dramatically shorten the trial and error cycle required to build our templates.
Built in “Key” Lookup
NetSuite is not obligated to give us the Built-In “Key” lookup. When NetSuite provide us data elements to work with, they are provided as Hashes. The Hash is effectively a collection of name:value pairs commonly available in most object oriented development languages. Sometimes these are called dictionaires because with the name, you can get its value or meaning. In general, when you have a Hash, you can loop through all the elements if you have the “Keys”. The Keys are a list of all the names located within the Hash. This is perfect if we want to output every field name and its related value. Imagine the time saved if you could spit out all the data NetSuite passes to us so we can see it in one place and perform cut-and-paste field name operations during our template development?
The Keys are available to us at the very top of the tree called the “data_model”. I have produced a template that at least let’s us look at the names of all the hashes (record types) being passed to us. From this, we can tell that the hashes are not case sensitive. For example, NetSuite’s sample templates show us ${companyInformation}. From my discovery work, ${companyinformation} works just as fine as well.
I suspect NetSuite is not providing us the Keys because it may reveal more data than what they want us to see and possibly breach security. But really, just because something is obscure does not mean it is secure.
NetSuite Advanced PDF Inspection Template
This template is quite basic primarily because Keys support is limited. Use it to inspect what record types are being passed to you.
<?xml version="1.0"?> <!DOCTYPE pdf PUBLIC "-//big.faceless.org//report" "report-1.1.dtd"> <pdf> <head></head> <body> <h2>Advanced PDF Template to Discover BFO Global Environment Information Passed from NetSuite</h2> <h2>Crafted by Marty Zigman</h2> <div><img src="https://www.prolecto.com/assets/footer/casual-cropped-marty-zigman-20121025-small.jpg"></img></div> <div><img src="https://www.prolecto.com/assets/footer/prolecto_logo_360x41_2007.jpg"></img></div> <p>Template Version: ${.version}</p> <p>Template Name: ${.template_name}</p> List (.data_model) <table> <#assign keys = .data_model?keys> <#list keys as key> <tr> <td>Key: ${key}</td> <td>Value: ${.data_model[key]}</td> </tr> </#list> </table> List (.namespace) <table> <#assign keys = .namespace?keys> <#list keys as key> <tr> <td>Key: ${key};</td> </tr> </#list> </table> List (.main) <table> <#assign keys = .main?keys> <#list keys as key> <tr> <td>Key: ${key};</td> </tr> </#list> </table> </body> </pdf>
NetSuite Enhancement Request
There is a NetSuite enhancement request for this capacity. Please vote for SuiteIdea: 296495 to help this enhancement get priority. Once it is in place, I will develop a template that will enumerate all the field names and values so we all can enjoy.
Related Articles
We are committed to this space. With some tools we built which are available to, all the data is now exposed. See below articles most relevant for NetSuite users working to get more out of the Advanced PDF/HTML tools:
- How To: Leverage NetSuite’s Advanced PDF / HTML Generation Tools
- Supercharge NetSuite Advanced PDF/HTML Templates
- Solved: Custom NetSuite Item Fulfillment Ship Notifications
- Framework for Generating Custom NetSuite PDF Files
See Related Articles
- Learn How to Bring NetSuite Subsidiary Data into Advanced PDFs
- Video: How to Extend Advanced PDFs with Content Renderer Engine
- How To: Password Protect NetSuite Generated PDF Files
- Supercharge NetSuite Advanced PDF/HTML Templates
- NetSuite Lookup: Get Custom Record Script ID from Internal ID Integer
- How To: Leverage NetSuite’s Advanced PDF / HTML Generation Tools
An easy hack to get fields that are present for Freemarker is to append &xml=t to the transaction you are viewing. This doesn’t give you what you have available from company but gives most of what you can get from record.
Good article.
@Corey Hunt – Thanks for that hack. Really helpful.
Hi Marty, i want to create sales order pdf using suitelet. I wrote the code to do that, but the XML Parser in not parsing the Logic directives how to write those in script. pls help me.
Hi Vinod,
I find that during development, it is pretty easy to “mess up” and the system won’t parse the data. A few thoughts:
1. Use an XML validator tool to help you see what’s wrong with the XML. Here is one that should work well
2. Work with the XML outside of the NetSuite tool in your favorite editor. This does require cut and paste. I like to frequently save my files with new names to help version in case I need to go back in time.
Good luck! I plan on writing many more articles on this subject as we have done some new things to get more power of out this framework.
Marty
Hi Marty
I have clear the suite foundation exam and now preparing for Certified developer.
Please let me which study material should I refer.
Any specific Netsuite PDF, Video,Help topics.
Thanks in advance.
Hello Sachin,
I don’t have any specific recommendations. We all have different learning styles. The SDN program offers a number of resources for you to peruse. Good luck!
Marty
Hi Marty,
You mentioned you edit the XML in another editor and copy and paste it into the NetSuite interface.
Our team has streamlined this by uploading all the templates to the File Cabinet and using the SuiteScript plugin for eclipse to edit straight in eclipse. We use imports in the NetSuite interface to load the template from the File Cabinet.
This gives you the a) advantage of an easy workflow (e.g. ctrl + u automatically uploads to NetSuite so it’s as simple as pressing ctrl + u and refreshing the template to try any new changes) and b) the ability to use version control (e.g. git) to collaborate efficiently.
Hi Bob,
That’s a great idea. Much faster to work in that context. Thank you. These days, we are pushing the Advanced PDF / HTML tools pretty hard. Our capacity now to execute multiple searches that we can join together and then present to the environment is solving many client problems. See article: Supercharge NetSuite Advanced PDF/HTML Templates
Marty
Hey Marty,
Today I started to build my first Advanced PDF template for an invoice. All is fine except I need to show a line of text based on tax code. Now I can pull the tax code but when I do a compare it is never true:
text to be shown here…
If I do a != my text is shown. However, the value returned from taxcode on the line item shows VAT:Standard GB.
Any thoughts or advice? And how can we reach the field ids from the line item on an invoice, is this possible?
Many Thanks!
Hi,
These days, we are using the CRE Tool to get exactly what ever we need. No more copying fields and so forth just because NetSuite can’t reach the data. We feel so blind to the data set with NetSuite’s standard implementation. It’s trial and error which burns a lot of time. Let me know if you want the tool. It’s a short services engagement to make sure you are productive.
Marty
Marty,
Great article. I was hoping there would be some way to pull out the various field IDs, but cannot get anywhere. I noticed on the advanced PDF pick tickets, there is no longer a column for the preferred bin of the item. I’ve tried numerous field names and cannot get anywhere. Do you have any ideas on how to go deeper into this? I know your article was published in 2014… I’ve tried record.item.binnumbers, nothing…I’ve even tried record.item.item.binnumbers which DOES get me to the bin sublist, but getting the preferred bin for the given location would be a mess of code!
Hello Dan,
Yes, this is solved using our CRE Tool. We basically can define the saved search and target the preferred bin location. How are you printing these?
Marty
Looks interesting. Right now we’re still using the non-Advanced PDF forms, which has a printable field for “Bin Numbers”. This is the only thing holding us back from using the new Advanced PDF pick tickets.
Dan,
I setup a sample transaction in our system to confirm I could see the concern. Here is my assessment:
1) We definitely can reproduce the Pick Ticket to include the Preferred Bin information. This includes logic to determine availability like you can see in the classic form.
2) The challenge though becomes driving the enhanced CRE based PDF through NetSuite’s Bulk “Print Picking Tickets” function. We can’t get control over that element as it is wired to native templates.
3) To solve that, we would need to create a SuiteLet that emulates that function so that we can create bulk or batch print mechanisms. We have done solutions like that before.
However, I am not sure the effort is worth it. While the CRE tool can give you control, if you need a new batch mechanism to drive it, it diminishes the value because we need to build that. The “old school” approach to start writing custom fields also has weakness. Traditionally, you would create a custom transaction column on the Sales Order to drive this. However, the information about item availability is dynamic and thus, information in the custom field would be stale at the time to print the tickets. We may be able to create a custom formula field to get the information in play but there are times (or really, specific use contexts) the data won’t calculate.
Marty
Hey Dan,
We ended up creating a Suitelet to do this. It wasn’t easy, but it works well as we needed a way to print pick-pack tickets customized for each of our customers.
Cheers,
Mark
Mark,
Thanks for sharing. I suspect I could couple your SuiteLet to the Content Rendering Engine to get maximum control over the experience. Did you end up creating custom field(s) to hold the data so that the Advanced PDF template could read it?
Marty
Thanks for the quick responses guys. I was hoping it was something obvious that I was missing, but unfortunately, that’s not the case. I’ll see if I can file something with NetSuite to get this back. For now, I think we’ll continue with the standard PDF templates.
Thanks!
Hey Marty,
I just reviewed the code really quick, and it appears we ended up using the OzLink custom fields, but I don’t see any new ones created. I’ll email you a screenshot of how this looks form wise on our end now.
It has actually worked pretty well since we had discussed initially with you our problem.
Cheers,
Mark
Hi, Marty.
I found this post very useful to find out the real data structure being passed on to the template. What I’m really struggling is to find out the correct syntax to enumerate the fields (for the record data, for example).
Is this only a matter of using the correct syntax to get the second-level fields keys or is there another level of complexity I’m not aware of?
Edson,
I agree. It’s trial and error. Our Content Renderer Engine allows us to inspect all the variables and is useful in this context. See this image . Here is a supporting article.
If you are doing a lot of this work, we can give you the tool with a bit of coaching.
As always – awesome. Do you know how to change page size from LETTER to A4?
Hello Trav,
Did you see this reference? Give it a try. It may do the trick: https://bfo.com/products/report/docs/tags/atts/size.html
Hai sir i have one doubt i get the standard sublist from transaction record but i did’t get the custom sublist can you please help me using in freemarker advanced PDF
Hello Brus,
The trick with custom sublists is to determine if you can get to the ‘recordmachine’. In the browser, right click and inspect the tab to see if you can discover the name. Of course, we solve all of these ‘simple but hard matters’ with our Content Renderer Engine tool.
Marty
I would like to know if there is something similar for the E-Document Templates?
I am trying to access custom fields of the client address, but I am not getting it: customer.address.field … For the XML Electronic Invoice.
Thank you!
Eduardo,
We can get to all the address fields via saved search when we use our Content Renderer Engine.
https://blog.prolecto.com/2015/06/01/supercharge-netsuite-advanced-pdfhtml-templates/
Feel free to reach out to me and we get create a quick engagement so you can learn how to use it.
https://www.prolecto.com/contact-us/
Thank Marty very helpful.
Although one thing i’m struggling with is accessing the values of a saved search passed to the Advanced PDF via renderer.addSearchResults.
As the search columns are Grouped or Summed i dont seem to be able to get at the data. Even though it knows the correct number of results when i do the list, but the key is empty.
Key: ${key}
Search data looks like this:-
values: {
"GROUP(item.upccode)": "5055956017850",
Any help would be appreciated. 🙂
The built-in NetSuite tools do not allow you to get to grouped/summarized information. This is one of the major reasons we have our Content Renderer Engine. We have a new version that now uses SuiteQL. The tool is available without a license charge to all of our clients. Here is an article on it where we use the tool to reach aggregate data and instead of going to a PDF (easy), we drive charts.
https://blog.prolecto.com/2016/05/18/how-to-insert-multiple-netsuite-aggregate-saved-searches-to-google-charts/
Marty
Have you by chance ever discovered a way to have elements render or not based on whether the final document output will be html or pdf? Some things I can solve by using inline attributes that override the inline style attribute since they are valid in xml but ignored in html but I would really like a way to have freemarker skip inclusion of elements I only want included if the document will ultimately be rendered as a pdf. There are many pdf only elements (shape tags, charts etc) that BFO supports and I would really prefer that code not be present when rendered as html. Any insight you might have on that front would be greatly appropriated. Thanks.
Hello Brent,
Our Content Renderer Tool will easily allow you to discern this so that you can conditionally drive the markup. See this article with pointers to many related articles:
https://blog.prolecto.com/2021/04/10/content-renderer-engine-2-0-with-netsuite-suiteql/
Marty
This template doesn’t work anymore, right?
I got this error:
The template cannot be printed due to the following errors: Error on line 18, column 36 in template. Detail… For “${…}” content: Expected a string or something automatically convertible to string (number, date or boolean), or “template output” , but this has evaluated to a method (wrapper: com.netledger.app.accounting.print.nsformat.NsFormatBooleanMethodModel): ==> .data_model[key] [in template “template” at line 18, column 38]
Thank you Marty for this article.
One question. How do you append the Advanced PDF with a pdf from your computer c drive.
I tried
The code works with field_id as https://catalog.belden.com/techdata/EN/9207_techdata.pdf
However when I wan to use a local pdf file with the field_id as C:\VIP.pdf it throws below error
“Error reading external PDF from C:\VIP.pdf”
Any help would be appreciated.
Ani Raj
Hello Ani,
Please see this article for how to include a PDF inline to one being generated in NetSuite:
https://blog.prolecto.com/2018/05/06/how-to-produce-a-netsuite-consolidated-invoice/
Then, you must get your PDF up on a server (NetSuite) for this to work. The NetSuite servers can not see your local drive.
Marty
Thank you Marty,
So it is either a file on Netsuite server or on a web address, but can’t be one a local drive. Got it, thanks