The Create Quote PDF capability and the option to enable PDF generation was introduced in version 9.0.1905.2010 of Dynamics 365 CE. It can be enabled in the App Settings of the Sales Hub and it will add two buttons to the ribbon of the Quote form: ‘Create PDF’ and ‘Email as PDF’ of a selected Word template.
The big question is whether this option is or will be available for other entities as well? The answer is: technically it is. I stumbled upon a blog post on the website of Thrives where the web requests were analysed when calling the ‘Create PDF’ action. It turns out to call the Web API endpoint:
https://{org}.{region}.dynamics.com/api/data/v9.0/ExportPdfDocument
with the following JSON submitted in the body:
{
"EntityTypeCode": 1084,
"SelectedTemplate": {
"@odata.type": "Microsoft.Dynamics.CRM.documenttemplate",
"documenttemplateid": "153dc496-d79d-e711-8109-e0071b65ce81"
},
"SelectedRecords": "[\"{E3A79DA1-9B91-E811-8133-E0071B65CE81}\"]"
}
The response will contain a “PdfFile” attribute which is a base64 representation of the PDF file.
{
"@odata.context": "https://{org}.{region}.dynamics.com/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.ExportPdfDocumentResponse",
"PdfFile": "JVBERi0xLjUNCjQgMCBvYmoNCjw8L1R5cGUgL1BhZ2UvUGFyZW50IDMgMCBSL0..."
}
The blog post ends with the possibilities of adding this code to a workflow assembly…. but our belief is #NoCode so let’s try this with Microsoft Flow. Yes we can just do it with Flow!
Here is the overview of the Flow:
The scenario is to start a Flow from an Opportunity record and select the Word template and the option to export the PDF document: Add to Note, Send by Email, Save to SharePoint.
We use the List records action and set the filter on the Name of the Document Template -assuming it’s unique- to get the id of the selected Document Template with the expression in the Compose action: first(body(‘List_Document_Template_record’)?[‘value’])?[‘documenttemplateid’]
Beware that the @odata.type key must be added like this to prevent an error: “@@odata.type”
To call the Dynamics 365 Web API we use the ‘Invoke an HTTP request’ action from the ‘HTTP with Azure AD’ connector, so we don’t have to deal with the authentication. Just fill out the base URL of your Dynamics 365 CE instance to sign in.
In the ‘Invoke an HTTP request’ action we do a POST request to the Web API endpoint ExportPdfDocument.
Then we parse the JSON of the response to get the “PdfFile” attribute for further processing. In this case I’ve only implemented the ‘Add to Note’ option. The Create record action will create a Note record with the PDF document as attachment and linked to the Opportunity record with the Regarding field.
Now let’s test the Flow by starting it from an Opportunity record.
Run flow will result in a new Note added to the Opportunity record, including the PDF document as attachment.
This will also work for custom entities. With the Metadata Document Generator plugin for XrmToolBox you’ll able to get the EntityTypeCode (Object Type Code) for your custom entities.
What about standard CDS? I’ve copied the Flow to the default CDS environment and modified it to use Word templates for Accounts. And it works, yes! So we have native PDF generation in CDS now.
Hi Stefe,
I am trying to replicate the first step. How are you accessing the Document templates. I don’t find that options in Flow triggers or actions.
I use the List records action of the CDS connector, but you know that now because we’ve got in touch via LinkedIn 😉
As interesting as this may be to nocoders and as good as it is as an example, don’t ever do it. To create a quote document from an opportunity completely flies in the face of what we are doing with CE. Quotes are like contracts. CE enforces versioning on quote records to substantiate the official nature of such a document. Then there is the whole sales projection & planning aspect of the app which runs through the tracking of quotes. This wonderful example is in action just a circumvention of the system and of management.
Good point, the Opportunity Summary is for internal use only. This is just an example to prove that is works.
hello, do you also have an example of sending it with email or saving it to sharepoint instead of adding it to a note ?
Hi Steven,
See blog posts:
Sending it with email: https://sergeluca.wordpress.com/2019/08/15/dynamically-create-a-list-of-e-mail-attachments-with-microsoft-flow-use-base64/
Saving it to SP: https://veenstra.me.uk/2019/10/17/store-your-files-in-the-common-data-service-with-microsoft-flow/
If you want to send an email with PDF attachment you need to convert ExportPdfDocument output to Base64ToBinary format.
base64ToBinary(body(‘Parse_JSON’)?[‘PdfFile’])
Do we also need to convert while saving it to the sharepoint. Since I cannot open the file, when I save as is.
Schema I’ve used is :
{
“type”: “object”,
“properties”: {
“PdfFile”: {
“type”: “string”
},
“@odata.context”: {
“type”: “string”
}
}
}
Hi Mohammed,
You need to use the function base64ToBinary to convert the content of the pdf file before saving it to SharePoint.
Hi,
Is it possible to do group table data in word template. If so how?
Thanks
Hi Manish,
You can fill a repeating table with data but unfortunately you can’t group/sort/filter the data.
Hi, how to access the data stored in common data service? Can it be downloaded?
Hi Akash,
The common data service is/has an API so you’re able to access the data, or download it from the UI of a model-driven app (export to Excel), or from the maker portal (make.powerapps.com) and export the entity data.
Hi Stefan,
Hope you are doing great.
Thanks for such a nice article and I hope lot of developer will be taking help from it. I also tried at my side to generate the pdf using the method you have explained however I am little unfortunate here to not to manage to run it successfully.
To be precise, I am getting error at “Invoke an http request”
{
“error”: {
“code”: 502,
“message”: “{\r\n \”error\”: {\r\n \”code\”: 502,\r\n \”source\”: \”aus-001.azure-apim.net\”,\r\n \”clientRequestId\”: \”43ad60b7-e7ce-4c76-a256-272e37462c59\”,\r\n \”message\”: \”BadGateway\”,\r\n \”innerError\”: {\r\n \”error\”: {\r\n \”code\”: \”0x80040216\”,\r\n \”message\”: \”An unexpected error occurred.\”\r\n }\r\n }\r\n }\r\n}”
}
}
Body of the request looks like
{
“EntityTypeCode”: 10404,
“SelectedTemplate”: {
“documenttemplateid”: “b568d6e9-0133-eb11-a813-000d3a799ab5”,
“@odata.type”: “Microsoft.Dynamics.CRM.documenttemplate”
},
“SelectedRecords”: “[\”{5f88e07c-6b32-eb11-a813-000d3a799ab5}\”]”
}
where as url is https://XXXXX.crm6.dynamics.com/api/data/v9.0/ExportPdfDocument
Would you be able to help me here and suggest me where I am getting wrong?
Regards
Megh
Hi Megh,
I am also facing the same issue. Did you find the resolution on this. If yes then please share the solution with me.
My flow is taking infinite time to “Invoke an HTTP Request”. Can anyone tell me if am I doing anything wrong?
Hello Experts,
I am getting timeout error at “Invoke an HTTP Request” step. Can anyone tell me if I am doing anything wrong here:
Thanks
Hello
Wondering how u make that “compose json” component when there is just “compose”?
tryed with:
{“EntityTypeCode”:10190,”SelectedTemplate”:{“@odata.type”:”Microsoft.Dynamics.CRM.documenttemplate”,”documenttemplateid”:”99507977-86db-e811-a96f-000d3a2bcd97″},”SelectedRecords”:”[\”{17382631-D550-EB11-A812-000D3AA96894}\”]”}
json(‘{“EntityTypeCode”:10190,”SelectedTemplate”:{“@odata.type”:”Microsoft.Dynamics.CRM.documenttemplate”,”documenttemplateid”:”99507977-86db-e811-a96f-000d3a2bcd97″},”SelectedRecords”:”[\”{17382631-D550-EB11-A812-000D3AA96894}\”]”}’)
Get response:
{
“error”: {
“code”: 502,
“source”: “europe-002.azure-apim.net”,
“clientRequestId”: “6b278fd8-81e0-4c97-8441-733d37586ce0”,
“message”: “The response is not in a JSON format.”,
“innerError”: “HTTP Error 500 – Internal Server Error\r\n”
}
}
Thanks
Hello Stefan,
I cannot get the Json to work correctly.
{
“EntityTypeCode”: 1084,
“SelectedTemplate”: {
“@odata.type”: “Microsoft.Dynamics.CRM.documenttemplate”,
“documenttemplateid”: “see below”
},
“SelectedRecords”: “[\”{@{triggerBody()?[‘entity’]?[‘quoteid’]}}\”]”
}
specifically below:
first(body(‘List_Document_Template_record’)?[‘body/value’])?[‘documenttemplateid’]
Following works for me:
{
“EntityTypeCode”: 1,
“SelectedTemplate”: {
“documenttemplateid”: “9d6916e4-1033-4e03-a0e3-d15a5b133a9a”,
“@@odata.type”: “Microsoft.Dynamics.CRM.documenttemplate”
},
“SelectedRecords”: “[‘{}’]”
}
/*
Important
@@odata.type instead of just @odate.type
and
“[‘{}’]” instead of “[\”{}\”]”
Hi Maurits,
Thanks for the heads-up. I’ve highlighted the original text about how to add @@odata.type, so people are aware.
In the “official” request payload the format is: “SelectedRecords”:”[\”{guid}\”]”
but if it works for you then Bob’s your uncle!
I am getting timeout error at “Invoke an HTTP Request” step. and taking lots of time . Can anyone has any solution of it.
This was working perfectly, however recently the Invoke HTTP step has started giving an error:
BaseResourceUri (https://XXX.api.crm6.dynamics.com) must be a base of the full url (https://XXX.crm6.dynamics.com/api/data/v9.2/ExportPdfDocument).
Has there been an updated that stops this solution working?