Office Graph API: New Document Based on a Template

calendar_month May 15, 2020

Recently I wanted to create a Word document based on a template via the Office Graph API and set a few metadata of the associated SharePoint ListItem. It turned out to not be a “straight-forward” job, which ultimately led to this article.

Problem in a nutshell

I have a library where documents based on a ContentType called AkDocumentDoc should be created. The ContentType in question is of type Document and has a few additional columns.

The AkDocumentDoc ContentType has a Word template assigned, so when creating an element based on AkDocumentDoc, a Word document with this template is also created. The template is called Pruef.dotx.

Now a logic - triggered however and wherever - should create new elements in this library based on this ContentType and set the metadata of the associated SharePoint ListItem.

All requests in this article can be sent either via the Graph Explorer or via Postman, for example. For the latter, the OAuth2 token must first be obtained and added to each query.

The Environment in a nutshell

For this example, I’m using a fictitious O365 subscription. For the respective Graph API accesses, we need various information.

The Environment (variables)

It’s about kirmizi Enterprises, who are now at home in the M365 world. The document library in question is on a SiteCollection for the HR department - /sites/halklailiskiler. The library - where documents are to be created - contains all profile documents of all employees and has the title Profillerimiz.

So those are the basics, and they should be enough for us to gather the further information, because the following is what we need:

NameValueDescription
hostnamekirmizient.sharepoint.comSharePoint Tenant
siteUrlhttps://kirmizient.sharepoint.com/sites/halklailiskilerThe SiteCollection in question
siteCollectionIdabcdd937c-a4a0-46dd-a7e2-5a82ab9f467aThe ID of the SiteCollection
siteId953edbdd-3289-4efd-bfdd-18cb1e63e0fdThe ID of the Root Site
listIdab026011-fa6a-411a-ba00-1ff9d6185849The ID of the library

The hostname and siteUrl parameters should be available to you, as should the title of the list.

The Site Id

Now we can select our site via the Graph API Explorer - the easiest way is with the Search API:

https://graph.microsoft.com/v1.0/sites?search=halklailiskiler

The result should look something like this:

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites",
  "value": [
    {
      "createdDateTime": "2019-06-13T13:05:16Z",
      "id": "kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd",
      "name": "halklailiskiler",
      "webUrl": "https://kirmizient.sharepoint.com/sites/halklailiskiler",
      "displayName": "halklailiskiler",
      "root": {},
      "siteCollection": {
        "hostname": "kirmizient.sharepoint.com"
      }
    }
  ]
}

Important here is the id key - kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd - we’ll need this for our further operations via the Graph API. I’ll add this value as “graph siteId” to the table.

Drive Id

Now we can find the drive id of our library - which differs from the listId in SharePoint. We get this information via the Graph API:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/drives

From the result, the id parameter provides the needed information:

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives",
  "value": [
    {
      "createdDateTime": "2020-05-12T09:35:06Z",
      "id": "b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9",
      "name": "Profillerimiz",
      "driveType": "documentLibrary"
    }
  ]
}

The drive id for our library would be b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9.

You can get the listId either by calling up the settings of the list/library or via Graph Explorer with the following query:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/lists?$filter=displayname eq '<List title>'

Solution in a nutshell

Theoretically, this is a very simple task with CSOM, SharePoint REST, or via PnP (Core). With the Graph API, the path is not so trivial. Here the document is created via the OneDrive API, and populating the metadata is done via the SharePoint API.

As long as everything necessary - Word template, ContentType, library, the table with variables, etc. - is present, the project can be realized via the Graph API.

For our project, Graph API permissions for SharePoint - I used Sites.ReadWrite.All here - and OneDrive - I also used a high permission File.ReadWrite.All here - are needed. These can be set in the Graph Explorer. If an application is acting in the background, these should be set in the App registration in Azure AD.

Create Document (OneDrive API)

Documents are created via a PUT request to the OneDrive API:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/drives/<graph drive id>/<folder>:/<filename>.<file extension>:/content

Since my documents are stored directly at the root level within the library, I must specify root for the <folder> placeholder.

My document should be named akblogtest123 and be of type docx. With the examples from the table and the two additional pieces of information, the query would look like this:

https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/drives/b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9/root:/akblogtest123.docx:/content

We can now paste the query into Graph Explorer and select PUT as the protocol. The request header should also include Content-Type: text/plain.

On success, a long JSON including download link is returned. The document should also appear in the document library.

Find Document (SharePoint API)

To successfully set the metadata of our document, we must access our document via the SharePoint API. Unfortunately, the response from the OneDrive API during/after creation doesn’t provide a value we can use with the SharePoint API.

We get this information with the following GET request. Note that the header Prefer: HonorNonIndexedQueriesWarningMayFailRandomly must be included. The FileLeafRef field is not indexed - resource-intensive query, this must be confirmed with Prefer:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/lists/<listId>/items?select=id&filter=fields/FileLeafRef eq '<filename>.<file extension>'

With the specifications and parameters we’ve defined/found, the query would look like this:

https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/lists/ab026011-fa6a-411a-ba00-1ff9d6185849/items?select=id&filter=fields/FileLeafRef eq 'akblogtest123.docx'

The response to this query is relatively small - it consists of the SharePoint ListItem Id:

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites(...)/lists(...)/items",
  "value": [
    {
      "@odata.etag": "\"75c3eb58-4ccc-4abe-8ef2-573eb5053cce,1\"",
      "id": "11"
    }
  ]
}

Our new document has the Id 11 - we need this Id for our next task.

Update Document (SharePoint API)

Now we can update the metadata of our document. We’ll use the PATCH method for this. Additionally, we must specify changes in the request header and provide a JSON as body:

{
  "fields": {
    "Title": "Test-Blog-AK",
    "FileLeafRef": "akblogtest123-update.docx",
    "ContentType": "AkDocumentDoc",
    "Test1": true,
    "Test2": false
  }
}

So we set the title to “Test-Blog-AK”, change the filename to akblogtest123-update.docx, provide the ContentType with AkDocumentDoc, and additionally set the boolean columns Test1 and Test2.

The Result

We get a response (JSON) that also contains our current changes. In the library, the picture then looks like this.

Finished in a nutshell (not)!

That was it. What you do with this information is entirely up to you. Writing takes longer again than finding out/implementing. In the future, I’ll actually keep this in a nutshell… or maybe not ;-)