Some seriously powerful (and free) PDF functionality in PowerApps and Flow
Hiya
Pretty much every time I run a project kick-off meeting involving tools like SharePoint or PowerApps, I always ask the group the following question:
“If you had <insert goal here>, how would things be different to now”?
(Replace <insert goal here> with any common IT platitude like “improved collaboration”, “knowledge management”, “big data analytics” or “digital transformation”. )
Now inevitably, someone will say something like “I would have information at my fingertips”. While that may sound like a cliche, these days, it is pretty easy to meet this goal. So easy in fact, that I can do it in around 10 minutes using a SharePoint Document Library, a quick PowerApp and a Flow of only 5 steps. I then get to be a smartass, hand them my phone and say “there you go – at your fingertips… That’ll be $250 thanks”.
Now if you want to score $250, I have bad news for you. No client has ever actually paid me when I used that line. But nonetheless, if you have been looking for a demo to show just how powerful these tools have become, then one is pretty good… If you check the image below, you can see I have a PowerApp that is browsing a SharePoint document library and irrespective of whether the document is Word, Excel or PowerPoint, is loading it as a PDF into PowerApps for preview. Better still, this solution is very much in the citizen developer realm…
A common use-case for this sort of solution is a field worker who needs to access schematic data like drawings or equipment documentation. I have also deployed something similar for quality management and safety apps. In all cases, users had a need to be able to retrieve documents quickly and easily…
How to build this app
This app consists of a simple PowerApp, a SharePoint doclib and a 5 step Flow.
Step 1: Create a document library in SharePoint – in my case I called my library “FingertipsBaby”…
Step 2: Start PowerApps studio and make a blank tablet app.
Step 3: Connect the document library as a data source using the SharePoint connector. Note, although document libraries are not listed by default, you can definitely connect them. Just scroll to the end of the lists and in the last box, type in the name of your document library…
Step 4: On the left side of the screen, add a blank vertical gallery and linked it to the data source.
Step 5: Add a label to the gallery and set its Text property to ThisItem.’{FilenameWithExtension}’. If you check the image below, you can see my three documents listed from my library. Not pretty I know, but this is a 10 minute demo remember?
Step 6: Go to the Action menu and click the Flow icon. in the Data panel that opens on the right, click Create a new flow. A new tab will open in your browser and log you into flow. A new flow will be created using a PowerApps trigger as shown above. Take the opportunity to give your flow a name, such as “PDF Viewer”
Explanation interlude:
Now at this point I feel some explanation is needed. We are about to use a few recent enhancements to Flow and SharePoint. Kudos needs to go to Brian Edwards who made me aware of a powerful new SharePoint API that among other things, generates thumbnails and PDF’s of documents. Brian already has a blog that explains how to do thumbnails – so consider this article a companion one that does PDF’s.
The key to the whole thing is an API called RenderListDataAsStream. This is a very powerful API that has a truckload of goodness, but for now I will keep it simple. I will pass a SharePoint List Item ID of a document to the API and it will give me back a PDF. The API call looks like this:
_api/web/lists/GetbyTitle(‘FingerTipsBaby’)/RenderListDataAsStream?FilterField1=ID&FilterValue1=<List Item ID>
In the body of the request, I need to specify the data I’d like returned. The parameter is called RenderOptions and looks like this:
{
“parameters”: {
“RenderOptions” : 4103
}
)
What is the deal with the number? Well, the documentation includes a table of different interesting things you can return, which you can do by adding the Values together.
Label | Description | Value |
ContextInfo | Return list context information | 1 |
ListData | Return list data | 2 |
ListSchema | Return list schema | 4 |
EnableMediaTAUrls | Enables URLs pointing to Media TA service, such as .thumbnailUrl, .videoManifestUrl, .pdfConversionUrls. | 4096 |
So we are asking this API not just to bring back the data associated with a list item, but also some additional useful stuff. The last entry is particularly interesting as it mentions a mysterious beast known as the Media TA service which I assume means “translation”. Basically what happens is if we total the numbers listed in the above table (4103), we will end up all the data we need to do PDF conversion.
Now at this stage, I don’t want to do an exhaustive examination of the JSON data returned by this API call, but I will call out a couple more things before we get to flow…
First up, you will find this entry…
“.pdfConversionUrl”: “{.mediaBaseUrl}/transform/pdf?provider=spo&inputFormat={.fileType}&cs={.callerStack}&docid={.spItemUrl}&{.driveAccessToken}
This parameter refers to a URL that will convert the list item to a PDF. All you need to do is access this URL. How easy is that eh? Now all of the stuff in curly braces are tokens but they are also returned as part of the API call, so they can be grabbed from elsewhere in the output. For example, {.mediabaseURL} is a few lines up and in my case is:
“.mediaBaseUrl”: “https://australiasoutheast1-mediap.svc.ms”
So if we find each token in the .pdfConversionUrl and replace them, we basically have converted our document to PDF without needing to store a PDF. The document can stay in its native format!
Back to Work…
Okay so let’s deal with this flow.
Step 7: Add an SharePoint action called Send an HTTP Request to SharePoint to the flow. Set the Site Address to the site that contains your document library and set the Method to POST. Set the URI to _api/web/lists/GetbyTitle(<docLib>)/RenderListDataAsStream?FilterField1=ID&FilterValue1=, where <Doclib> is the name you specified for the document library (for example, mine is _api/web/lists/GetbyTitle(‘FingerTipsBaby’)/RenderListDataAsStream?FilterField1=ID&FilterValue1=). On the end of the URI, click Dynamic Content and choose Ask in PowerApps as shown below:
Step 8: In the Body section, paste the following configuration (watch the quotes when pasting from this article):
{
“parameters”: {
“RenderOptions” : 4103
}
}
Step 9: Click the Save and then Test icon in the top right. Choose the option I’ll perform the trigger action and click the Save and Test button. Click the continue button and on the next screen, type in the ID number of one of the documents in your library and click the Run Flow button. Your flow will start and you can click Done. Assuming it worked, you will see a green tick of happiness.
Step 10: Click on the Send an HTTP Request to SharePoint action to expand it. We need to grab the output from the API call for the next action. Find the OUTPUTS section and copy the entire contents to the clipboard….
Step 11: Go back to edit mode and add a Data Operations action called Parse JSON to your flow. This action will allow us to make use of the output of the API call in the subsequent flow step.
Click the Use sample payload to generate schema link and paste your clipboard contents into the window and click the Done button. In the Content field, go to Dynamic content popout and choose Body from the Send an HTTP Request to SharePoint action.
Step 12: Add an Initialize Variable action to your flow. Name the variable PDFURL (or something similar) and set it to a string. Now we come to the most complex bit of the flow where we have to substitute the token we examined in the interlude earlier. Be careful here… this is the most likely place to make an error…
In the Value textbox, click the dynamic content flyout and find .mediaBaseUrl from the Parse JSON action…
Next, add the following text to the Value textbox, taking care not to delete what you just added in the previous step. <edit>Please note the image has a quote that is not meant to be there, so ignore it!</edit>
/transform/pdf?provider=spo&inputFormat=
Now we come to a slightly tricky bit. The next bit of content we need is the file type of the document we are dealing with. Now this is formatted as a single value array, so despite there being only a single value, we need to create an expression to handle it. Click the Expression tab and type in the following:
first(body(‘Parse_JSON’)?[‘ListData’]?[‘Row’])?[‘File_x0020_Type’]
For the uninitiated, this is known as Workflow Definition Language and is well worth learning…
Next, add the following text to the Value textbox, taking care not to delete what you just added in the previous step.
&cs=
Also be super careful here because at the time of writing, the cursor in this textbox can randomly move and wipe out your edits…
Now in the Value textbox, click the dynamic content flyout and find .callerStack from the Parse JSON action…
Next, add the following text to the Value textbox, taking care not to delete what you just added in the previous step.
&docid=
Now we come to another array that needs to be handled. This is the URL of the document we are dealing with. Click the Expression tab and type in the following:
first(body(‘Parse_JSON’)?[‘ListData’]?[‘Row’])?[‘.spItemUrl’]
Okay we are almost done… Add an ampersand ( & ) to the Value textbox, and then click the dynamic content flyout and find .driveAccessToken from the Parse JSON action…
Step 13: Add a the PowerApps – Respond to PowerApps action to the flow. Click the Add an output icon and choose Text from the list of output types. Name the output PDFURL and set the value to the variable you created in step 12 (PDFURL).
Ok we completed the flow. Save it and give it a quick review. It should look something like this…
Step 14: Back in your PowerApp, now, select the label gallery you created in step 5. From the Action menu, choose Flow and you should see your newly created flow listed. Click on it to add it to your PowerApp. Once added, the label will partially fill in the OnSelect property to run your flow.
Step 15: Complete the flow invocation by adding a reference to the ID of the document from the gallery by using the following function:
Set(PDF,PDFViewer.Run(ThisItem.ID))
This function will set a variable called PDF to the output from our newly minted flow. When you click on a file in the gallery, the flow will get called. In fact you can try this out before we move on. Press the play icon to test your app and click one of the files. Then go back into edit mode and click the Variables icon from the View menu. You should see a global variable called PDF that has a data type of record. Clicking the variable will bring up further details and if you click on the record icon, you will see the output returned from flow.
If you get this far, that’s great because you are almost home….
Step 16: From the Insert menu, Add a PDF Viewer from the Controls menu. Place it to the right of the gallery and set the Document property to PDF.pdfurl. (if you look closely at the above image you can see that PowerApps has decided that the output of the API is a record with a column name of pdfurl).
Now click on some of the documents… if you did it correctly, you will be seeing PDF’s! Wohoo!
Conclusion
Not bad eh? A 5 step flow has enabled a very powerful use-case and once again showcases how well PowerApps and Flow work together. I should note that there are three recent innovations that have enabled this scenario, namely the recent Send an HTTP request to SharePoint action, the RenderListDataAsStream API and the Respond to PowerApps action.
I need to also give credit to my daughter Ashlee, who actually figured a lot of this out earlier in the week. We will record a video on this fairly soon to accompany this post.
If you got value out of this post please let me know. I’d love to hear of other use-cases of variants on this approach
Thanks for reading…
Paul Culmsee
&inputFormat=” – where should this quotes end?
woops thats a typo… no quote (I edited the article to make it clearer)
Do see pdf url in output
but no data in viewer
When I try and access the pdfurl in browser get the folliwng message
{“error”:{“code”:”generalException”,”message”:”\”pdf”}}
That is very likely to be because you cut/pasted from this article. Retype all the quotes as they tend to get reformatted
I can transform to a thumbnail but not pdf
Get exception :-
{“error”:{“code”:”generalException”,”message”:”pdf”}}
Did not do copy paste
Tried it end to end
transform/pdf fails –
{“error”:{“code”:”generalException”,”message”:”pdf”}}
transform/thumbnail works by passing width and height params – it seems it has something to do with the api response
probably need to send me your PDF URL so I can compare to mine…
Okay for reference to other readers, the issue is that a PDF cannot be converted to a PDF. You can only do this for Word, Excel and PowerPoint. Admittedly I have not tested other formats (except I can confirm Visio does not convert)
Great work Paul and Ashley!
I am also aware that on my blog there is a pending update on that blog I need to make because of quotes that shouldn’t be there, but were somehow inserted when I myself copied and pasted some code. Accordingly completely agree with Paul that should something perhaps not work should you have directly copied and pasted any code, it is quite possible that quotes may be there that should be there or vs. versa.
Equally correct that it will not “convert” all types of Microsoft Office application documents to PDFs.
I personally could not get it work with Excel documents. I did however also test converting images and HTML files to PDF documents, and interestingly enough those also worked, albeit those ‘test findings’ were based on the very limited testing I myself did.
That same API can also be used to convert documents in all of the above formats as well as PDF documents to Images displayed in Image controls in PowerApps, albeit , based again on the limited testing I did, only the first page of the document converted.
This too has an interesting potential use case – should you want to display content abstracted from a document stored in SharePoint within your app, however you may perhaps have a requirement wherein you do not want users of the app to copy and paste the actual text from the document to use somewhere else. Similar in concept to simplistic IRM…
What I could not get working as hard as I tried was steaming videos within an app using that API. Perhaps someone else can figure that out!
Great article, thank you.
How would you adapt the above to work with libraries that contain pdfs as well as Word, Excel and PowerPoint files?
Easy 🙂 First in flow use a condition to test file extension and if it is PDF, you instead use my floxy pattern… https://www.youtube.com/watch?v=NTp77benqfQ
My document library has both folders and files at the root level. How do I skip the folders and display files within the folders?
Hi,
Is there a way to do this with regular Sharepoint list items, not a Documents Library, how would the PDFURL variable be created in that scenario? or perhaps it only works for documents
Awesome – and this also gives you a *fourth* way to convert html to PDF!
Save your html as a text file in a SharePoint library and use that file’s ID in the Send HTTP request to SharePoint step.
After the initialize variable step, add an HTTP step with request type GET and where the uri is the variable (PDFURL).
Then add a SharePoint – Create File step. Set Address, path and name as desired and set the File Content to the Body from the HTTP step. 🙂
Only issue I’m having with all this at the moment is getting the GetbyTitle to accept library names with spaces in them…
Thank you for this great post !
My document library has both Document Sets folders and files at the root level and i found an issue : it only works for documents at the root folder ?
Ah yes, so you can work around this. You can call the API with a CAML query and get any item that way…
Thanks, i managed to get it worked while adding a FolderServerRelativeUrl parameter in the body of the request.
Hi.
I get stuck in step 12 because .mediaBaseUrl doesn’t appear.
What should I do?
I’m getting the “Cannot open the PDF file” in the PDF viewer control, any ideas why?
I got this to work for Word documents, but not excel…any suggestions??
Great post! I too am stuck on Step 12 because .mediaBaseURl does not appear for me. Any ideas on what I might have done wrong?
Never mind, I figured out the issue. I had to manually type in the code for steps 7 & 8, then when I ran the Flow it did not error out. I missed the error on the Flow the first few times that I tried this.
Sharing is caring 🙂
Further to the initial blog I posted wrt rendering highly optimised images from SPO, I have since shared the sample code for the 2 demo apps depicted in that blog “as-is”:
i.e. no subsequent changes / fixes or enhancements made since I published the blog late June.
The two sample apps and the Flow they both consumed make use of a number of interesting patterns.
Enjoy!
https://github.com/Office365Master/SharePoint-Images-Powered-Up-In-PowerApps
https://www.myconsult.co.za/residue/brians-blog/
I keep getting a fail on my flow with the error:
unauthorized
I take it that the flow is running anonymously, my organization only lets members of the domain access the sharepoint sites we have.
Hi Paul
I am stuck at step 11 When I click on “Use sample payload to generate schema” and paste in the payload from the previous step and click on “Done”I get an error “Unable to generate schema: Syntax error : Invalid character. I have copied from the previous step and pasted into the are without copying anywhere else first.
IS there another way of doing this step ?
Thanks
Nigel
Hi Paul,
Great article, well presented and complete. However my flow does not run, I get the following error in the Initialize Variable step, what am I doing wrong here? My flow is prepared just like the one you presented. Thanks…
Unable to process template language expressions in action ‘Initialize_variable’ inputs at line ‘1’ and column ‘1939’: ‘The template language expression ‘first(body(‘Parse_JSON’)?[‘ListData’]?[‘Row’]?[‘File_x0020_Type’])’ cannot be evaluated because property ‘File_x0020_Type’ cannot be selected. Array elements can only be selected using an integer index. Please see https://aka.ms/logicexpressions for usage details.’.
I suspect your API call returned no results and therefore some json was missing.
If there is no file found, you will see a line “Row”: [] in the ListData section like so…
{
“wpq”: “”,
“Templates”: {},
“ListData”: {
“Row”: []
But if you did retrieve a file, there will be heaps of data inside Row like so…
{
“wpq”: “”,
“Templates”: {},
“ListData”: {
“Row”: [
{
“ID”: “1”,
“PermMask”: “0x7ffffffffffbffff”,
[snip]
}
The output needs to be in the latter format before moving to the next step
To add to that wrt the API call potentially returning no rows (aka list items) – this was one of the reasons in my \’related\’ blog I hardcoded the \’Site Assets\’ library as this library is automatically provisioned inclusive of a couple of stub images (__siteIcon__.jpg & __siteIcon__.png).
If the library you reference in your Flow contains no items when you create the Flow, you may well find the schema generated doesn\’t include the full JSON payload you will need so ensure you don\’t try create the Flow referencing a list with no list items / documents.
Thank you for the prompt response, much appreciated!
The API call in Send HTTP Request to SharePoint does have Row definition as shown below, the output includes a reference Document1.docx. The Initialize Variable step with the error says “property ‘File_x0020_Type’ cannot be selected. Array elements can only be selected using an integer index. ”
{
“wpq”: “”,
“Templates”: {},
“ListData”: {
“Row”: [
{
“ID”: “2”,
“PermMask”: “0x7fffffffffffffff”,
“FSObjType”: “0”,
“HTML_x0020_File_x0020_Type”: “”,
“UniqueId”: “{0205ACCF-AC08-4C24-BC10-xxxxxxxxxxxx}”,
“ProgId”: “”,
“NoExecute”: “0”,
“ContentTypeId”: “0x010100D5DAB0A9E89E3443879D120C340176A3”,
“FileRef”: “/service/it/ITCustomerInfo/CustomerInfo/Document1.docx”,
“FileRef.urlencode”: “%2Fservice%2Fit%2FITCustomerInfo%2FCustomerInfo%2FDocument1%2Edocx”,
“FileRef.urlencodeasurl”: “/service/it/ITCustomerInfo/CustomerInfo/Document1.docx”,
“FileRef.urlencoding”: “/service/it/ITCustomerInfo/CustomerInfo/Document1.docx”,
Then I would double check step 12 with the first() function. Somewhere along the line you are trying to access the array of rows as if it is a single row and it is telling you “no you need to specify the item you want”
My bad, sorry the first function was the culprit. Thanks much for pointing me in the right direction.The right parenthesis closing out body in first(body(‘Parse_JSON’)?[‘ListData’]?[‘Row’])?[‘File_x0020_Type’] was at the end of the expression instead of after the Row closing bracket. The flow now runs and generates a PDF Record Output ‘pdfurl’ with out put shown but the PowerApps PDF Viewer Control can’t open the .docx file…clicking the open in browser link in the control yields the following error…any ideas? Thanks for all the help!
{“error”:{“code”:”generalException”,”message”:”Invalid URI: The URI is empty.”,”innererror”:{“code”:”General_UriFormat”}}}
Got It! Found another typo in the Initialize Variable call with first(body(‘Parse_JSON’)?[‘ListData’]?[‘Row’])?[‘.spltemUrl’].
I read the capital ‘I’ in .spItemUrl as a lower case ‘L’ instead of an ‘I’ for ‘Item’ as in ItemUrl. Again sorry for the additional post and thanks again Paul and Ashlee for a great Flow!
Has anyone figured out how to make this flow handle PDF documents as well as it does Office documents?
I got this working on a document library and everything was fine but now, for seemingly no reason, the output from the HTTP request has no rows:
{
“wpq”: “”,
“Templates”: {},
“ListData”: {
“Row”: []
The flow runs successfully but the final url leads only to this:
{“error”:{“code”:”generalException”,”message”:”Invalid URI: The URI is empty.”,”innererror”:{“code”:”General_UriFormat”}}}
The library contains only Excel files.
Any ideas where to look first? I could do with getting this up and running again quickly.
Hi, nice article and I managed to make it work (after trial and error on some ‘ and ; -changes)… but… finnaly… no answer to my quest on finding a way to create a PDF from xlsx in PORTRAIT instead off the landscape I get all the time…
I don’t want to pay a third-party for doing this…
Anyway: i learned a lot by executing your good step-by-step explanation: thanks for it!
Just discovered in my quest for a low-cost solution to create a pdf from excel (in PORTRAIT, because standard apps in flow always convert in landscape…) with Microsoft Flow: Encodian App. Works fine!
Hey Paul & Ashley,
You might wanna check my latest blog out 🙂
https://masteroffice365.com/leveraging-graph-in-powerapps-and-flow/
Spoiler – PDF conversion from doc, docx, epub, eml, htm, html, md, msg, odp, ods, odt, pps, ppsx, ppt, pptx, rtf, tif, tiff, xls, xlsm, xlsx…
Hi Paul !
Great Article, it helps me a lot.
i can display pdf from a sharepoint Library.
but i’m stuck when i want to save the flow.
i have the following message :
“The validation of the model failed: “The “Parse JSON” action or actions referenced by “entries” in the “Initialize_the_variable” action are not defined in the model. ».”
Any idea of a solution ?
thanks
Hi Guys, i wanted to know if i can adopt this read data from PDF’s and have the output in .xls format. i’ve tried several solutions but i’ve reached a dead end 🙁 any help will be greatly appreciated. thanks.
Hi everyone,
I am really new to the amazing world of power apps and I would like to ask you a simple question:
My purpose is to build a power app able to convert a single power point into a PDF but I don’t want to “click” on the file name (i want automatically display of the PDF in the power app).
Is this possible?
I wonder how this could be used to populate a PDF with data from SurveyMonkey. In my case, my Flow trigger would be a new SurveyMonkey response. My template would be an HTML document with the data plugged in.
I keep having the following error in Initialize Variable:
Unable to process template language expressions in action ‘Initialize_variable’ inputs at line ‘1’ and column ‘1962’: ‘The template language function ‘first’ expects its parameter be an array or a string. The provided value is of type ‘Null’. Please see https://aka.ms/logicexpressions#first for usage details.’.
Any ideas? I’ve tried lots of different suggestions, but it fails here every time.
Thank you Paul and Ashley. I love when people post solutions like this and as I continue to understand PowerApps plan to share useful tips as well, like a phone number mask solution I have come up with.
Having trouble with getting this to work. I am at the step “Send An HTTP Request” and when I Test I am getting this error “message”: “Not well formatted JSON stream.
Hello,
thanks for the great article. I have a question: Is it possible to use the HTTP-functions standalone in a flow (without powerapp) to convert a wrod document in a SP doclib into a PDF and save it as a new file to the sam SP?
How would I Need to modify the existing code?
I think if you use this article you get what your your looking for Olaf.
http://johnliu.net/blog/2017/10/generate-any-pdf-documents-from-html-with-flow
One Drive seems to have the Flow Feature that can convert files. I have set this up and added a couple steps to send the file back to SP and delete the file in OneDrive afterward.
Hi Jerome,
thanks for the quick reply. I am aware of the fact that this is possible with OneDrive. I am afraid that this will not work for me because I might loose metadata this way (data stored in website content type columns). These data are used within my word form ans must be included in the final pdf.
I admit I haven’t tested this with OneDrive, but copying my word file to a doc library without the same content type does not work, all metadata is lost this way.
Hi Jerome,
I got “Not well formatted JSON stream” too and I solved the issue. Beware of the ” in body section.
Hello,
Thanks for great article on PowerApss and flow integration. Can we open attachment of list using flow in PDF control.
Hello,
Can someone please share how to tweak this a bit further to show a pdf file as pdf as well.
This app isn’t able to display PDFs.
Thanks.
Looks like a recent update to flow has broken my flow. Did anyone else experience this?