Use Microsoft Graph to create SharePoint items

Use Microsoft Graph to create SharePoint items

In my last blogpost I listed lots of capabilities how to query and filter SharePoint items with Microsoft Graph. This post will concentrate on the creation and update of SharePoint items.

Content

Create vs Update (POST vs PATCH)

In this post there will be less endpoints listed but more body requests shown. The main difference for create vs update is, for create an item a POST request is send against the items endpoint

POST https://graph.microsoft.com/v1.0/sites/{site-id}/lists/{list-id}/items

While the update is a PATCH request against a specific item:

PATCH https://graph.microsoft.com/v1.0/sites/{site-id}/lists/{list-id}/items/{item-id}

Once the endpoint is selected the item, respectively its attributes and field contents needs to be transported. In general this is done the same for POST and PATCH operations but the difference slightly depends on the type of field.

Textfield

The body request for field content to write looks like the following. This first example only treats a bunch of textfield content:

 {
   "fields": {
     "Title": "Pat Pattinson",
     "Firstname": "Pat",
     "Lastname": "Pattinson",
     "Street": "Adelaide Ave"
   }
 } 

Number / Currency

For numeric or currency values simply the quotes are omitted.

{
   "fields": {
     ...
     "Street": "Adelaide Ave",
     "StreetNo": 118,
     "Salary": 1000.8
   }
 } 

Yes/No

Yes/No or boolean also simply omit quotes and accept true and false as value (but no 1 or “1” as alternative!)

{
   "fields": {
     "Title": "Pat Pattinson", 
     "KeyEmployee": true 
   }
 } 

Datetime

For Datetime fields the ISO format is used. In my last part I already mentioned this. For write operations three different variants can be used:

{
   "fields": {
     "Title": "Pat Pattinson", 
     "HireDate":"2021-02-01",            // Date only
     "HireDate":"2021-02-01T00:00:00",   // Date and Time, local time
     "HireDate":"2021-02-01T00:00:00Z"   // Date and Time, GMT
   }
 } 

As mentioned in the comments to the right of each date: It’s either possible to write a date value only or to add a time value and either take it for local timezone or explicitly mark it as GMT. To be on the save side I’d prefer ensure the right time locally and change that to GMT before writing to Microsoft Graph.

Lookup

As mentioned in my previous post, Lookup fields consist of two fields that can be retrieved: A field called <Fieldname> and a field called <Fieldname>LookupId. While the former one contains the more interesting value, the latter one contains the itemID pointing to the item inside the Lookup list. For write operations that is the field which needs to be written. This requires to know (evaluate first!) the ID of the lookup item. Once available the body request is as simple as the above ones:

{
   "fields": {
     "Title": "Pat Pattinson", 
     "EmployeeLocationLookupId":"5",
     "ManagerLookupId":"7" 
   }
 } 

Maybe it is worth to mention that although regularly integer numbers the LookupIDs are written in string format incl. quotes. Furthermore this is also valid for People fields which act es a lookup column, too. So you need the LookupID first, which can be found in the hidden UserInformationList or for the current user eventually in the current context.

Managed Metadata

To make it short: Managed Metadata cannot be written with Microsoft Graph when trying to POST or PATCH the <ManageMetadataFieldname>. As known from my first part a managed metadata field is returned as a complex JSon object consisting of several values (Label, TermID, LookupID in TaxonomyHiddenList).

"fields": { …
            "BirthPlace": {
              "Label": "Berlin",
              "TermGuid": "3fce150e-bd09-4075-b746-781be291a7e6",
              "WssId": 5
            },
            …
          }

Taking a look at the column definition ressource in Microsoft Graph several type specific properties can be detected. As there are “boolean”, “calculated”, “choice”, “number”, “lookup”. None of them except geolocation work with fields or columns that return complex data objects. Such as Hyperlink/Image or Managed Metadata. Those columns I don’t see supported yet in a write operation. But I also tried for geolocation and couldn’t find a way to write to them with Microsoft Graph.

But wait, I found a hint recently and with that evaluated a technical workaround at least for Managed Metadata. And as I like to detect and point out how things work under the surface I will show here:

Workaround Managed Metadata

When a Managed Metadata column is created it always creates a second corresponding “Note” column. That field is hidden and it’s name(s) correspond to the original managed metadata field out of the box .

<Field Type="Note" DisplayName="BirthPlace_0" StaticName="m03e2ac47e6646e6a5208e1a922d2708" ...
<Field Type="TaxonomyFieldType" DisplayName="BirthPlace" ID="{603e2ac4-7e66-46e6-a520-8e1a922d2708}" StaticName="BirthPlace" Name="BirthPlace" ... 
  <Customization>
    <ArrayOfProperty>
      <Property>
        <Name>TextField</Name>
        <Value>{7e503756-2df3-4ec0-a941-c3ac9d2f1632}</Value>

The DisplayName is <ManagedMetadataFieldname>_0 and the static name also is derived from the ID the original managed metadata name has. But this is not manadatory, it’s also possible to create corresponding hidden Note fields with PnP Provisioning and FieldXML for instance that have different names (Display- as well as Static- and InternalName). So the only ‘hard’ connection can be found in the <Customization> properties of the original field where the TextField is linked with the corresponding ID of the hidden Note (TextField). The shown FieldXML btw cannot be retrieved with Microsoft Graph so far so in case it’s needed the SharePoint Rest Api incl. authentications needs to be used.
But assume the information would be available and we know the corresponding internal field name (“m03e2ac47e6646e6a5208e1a922d2708” in above scenario). In that case the ID of a given term inside the TaxonomyHiddenList is needed on top. Therefore two queries are neeed. One for the TaxonomyHiddenList and it’s ListID:

 https://graph.microsoft.com/v1.0/sites/479ceff8-2da5-483b-ae0b-3268f5d9487b,c23c1e73-9fab-4534-badf-3f4cbc373d10/lists?$filter=displayName eq 'TaxonomyHiddenList' 

And another one for the item ID of the term:

https://graph.microsoft.com/v1.0/sites/479ceff8-2da5-483b-ae0b-3268f5d9487b,c23c1e73-9fab-4534-badf-3f4cbc373d10/lists/74a789ea-640e-4288-a501-08e3b06d9b94/items?$expand=fields&$filter=fields/Title eq 'Schwabing' 

Having that now the following syntax can be posted to the hidden Note field which will achieve that a correct managed metadata value is written:

{
   "fields": {
     "Title": "Pat Pattinson", 
     "m03e2ac47e6646e6a5208e1a922d2708":"1;#Schwabing|31ea81c1-a514-4cb6-a8ec-9983b4ebc1f7"
   }
 }

The value of the field consists of three parts <WssId>;#<TermLabel>|<TermGuid> while WssID is the LookupId inside the ‘TaxonomyHiddenList’. Unfortunatley this includes that the term must have been used on the given site already, otherwise it wouldn’t occur in that list of that specific side. I explained that in a post years ago and also how this can be handled programmatically but that is not directly related to Microsoft Graph.

A final downside of this workaround is that if the display of the entire path is enabled for the managed metadata field as well the corresponding path info is stored in the Note field, too, and the workaround would need to handle that also.

ListItem vs DriveItem

To create files together with metadata you need several requests:
The first request is to upload the file, mainly it’s content with a Stream. Only once that file is present it’s metadata can be updated quite the same way as seen above. A bit complex are the two different endpoint Urls one for the Drive to upload the file and one for the List to update the metadata.
For the drive a PUT request against

https://graph.microsoft.com/v1.0/sites/<SiteID>/drives/<DriveID>/root:/NewFile.txt:/content

Content-Type: text/plain

"This is a simple new text file."

The essential difference here is the /drives/<DriveID> part where the driveID has a totally different format than the listID from above which is a normal GUID format. Nevertheless the driveID is related to the listID and the blogpost from Mikael Svenson explains this in a very easy manner.

But for updating the metadata not only the listID is required but also the listItemID and that is different than the driveItemID, too. So with the response to the PUT request above a driveItemID is received. And having that the listItem can be requested this way:

https://graph.microsoft.com/v1.0/sites/<SiteID>/drives/<DriveID>/items/<DriveItemID>?$expand=listItem

This leads to the following result for example:

{ ...
   "name": "NewFile.txt", 
   "size": 26, 
   "parentReference": { 
      "driveId": "<DriveID>",
      "driveType": "documentLibrary", 
      "id": "01Y7EAUCF6Y2GOVW7725BZO354PWSELRRZ", 
      "path": "/drives/<DriveID>/root:" 
}, 
  "listItem": { 
    "id": "2", 
    ...
    "parentReference": { 
        "id": "da6da223-7ca1-4872-87bc-ada9e13c9a4f", 
        "siteId": "<SiteID>" }, 
        ... 
    },
    "fields": { ... } 
  }
}

From there the required PATCH endpoint Url can be constructed:

PATCH https://graph.microsoft.com/v1.0/sites/{site-id}/lists/{list-id}/items/{item-id}

Here the “id” of the listItem is used as the item-id, that is the numeric value 2 in this example. Having that endpoint we can start updating the corresponding listItem of the file as shown above.

Similar to its predecessor this post is intended to give an overview about options and syntax how to write SharePoint items (and files as a side-effect) with Microsoft Graph. Once again I encourage everyone to give feedback and to keep this post up-to-date over the time where for sure new capabilities will arise. Thank you in advance for this.

Markus is a SharePoint architect and technical consultant with focus on latest technology stack in Microsoft 365 and SharePoint Online development. He loves the new SharePoint Framework as well as some backend stuff around Azure Automation or Azure Functions and also has a passion for Microsoft Graph.
He works for Avanade as an expert for Microsoft 365 Dev and is based in Munich.
Although if partially inspired by his daily work opinions are always personal.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s