Generate PDF from Document Templates in CDS & Dynamics 365 CE using native Web API with Flow

Posted by

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.

21 comments

  1. 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.

    1. I use the List records action of the CDS connector, but you know that now because we’ve got in touch via LinkedIn 😉

  2. 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.

    1. Good point, the Opportunity Summary is for internal use only. This is just an example to prove that is works.

  3. hello, do you also have an example of sending it with email or saving it to sharepoint instead of adding it to a note ?

  4. If you want to send an email with PDF attachment you need to convert ExportPdfDocument output to Base64ToBinary format.
    base64ToBinary(body(‘Parse_JSON’)?[‘PdfFile’])

    1. 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”
      }
      }
      }

    1. 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.
      Export data

  5. 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

    1. 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.

  6. My flow is taking infinite time to “Invoke an HTTP Request”. Can anyone tell me if am I doing anything wrong?

  7. 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

  8. 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

  9. 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’]

    1. 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 “[\”{}\”]”

      1. 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!

Leave a Reply

Your email address will not be published. Required fields are marked *