Manage sites content types with Microsoft Graph (beta)

Context

Project Cortex uses AI for recognizing content types across systems then creates a knowledge network based on relationships among topics, content, and people (Viva Topics/SharePoint Syntex). This is my second article about Content services in Microsoft 365 and today I’m covering Content types APIs in Microsoft Graph (beta endpoint) which provide CRUD operations like creating/updating/deleting content types and their properties on tenant sites and hub sites. I recommend you to read my first article about Taxonomy APIs before continuing reading this one 😉 Now let’s jump into the content types world and see how it works together!

Permissions

To perform basic CRUD operations (read/write) on tenant sites and hub sites content types, you need at least the permission below in a Delegated (work or school account) or Application context:

Sites.ReadWrite.All

As a great use case here, you can grant an app read/write permissions to a specific site by using the brand new sites scoped permissions now available!

Permission required (Application context only):

Sites.Selected

Endpoint to grant app permissions to a specific site:

POST https://graph.microsoft.com/v1.0/sites/{siteId}/permissions

JSON request body:

{
    "roles": [
        "write"
    ],
    "grantedToIdentities": [
        {
            "application": {
                "id": "{AppId}",
                "displayName": "{AppName}"
            }
        }
    ]
}

Note: {AppId} is the Application (client) ID and {AppName} the display name of your app in Azure AD App registrations blade. It seems that there is a problem with sp.full control (owner) role which never worked on my side after multiple tries (Invalid request).

With that method, you don’t need to grant the Sites.Read.All or Sites.ReadWrite.All permission to your dedicated Azure AD application which respect the least privilege principle. It’s a really great new add from Microsoft Graph team to refine app permissions!

Perform basic operations

First we can retrieve all content types from a specific site, you need to replace {siteId} in the query:

GET https://graph.microsoft.com/beta/sites/{siteId}/contentTypes

Note: you can execute the same operation for a specific list too, you need to replace the {listId} in the query:

GET https://graph.microsoft.com/beta/sites/{siteId}/lists/{listId}/contentTypes

Then we can retrieve a specific content type metadatas, you need to replace {siteId} and {contentTypeId} in the query:

GET https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}

JSON response body:

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#sites('<SHAREPOINT_TENANT_ID>')/contentTypes/$entity",
    "@odata.etag": "\"1\"",
    "id": "0x01004DFD4872F860FD43AB3F75D69CF6141E",
    "isBuiltIn": false,
    "description": "Create a new list item.",
    "group": "Custom Content Types",
    "hidden": false,
    "name": "Test Content Type",
    "parentId": "0x01",
    "readOnly": true,
    "sealed": false,
    "base": {
        "id": "0x01",
        "description": "Create a new list item.",
        "group": "List Content Types",
        "hidden": false,
        "name": "Item",
        "readOnly": false,
        "sealed": false
    }
}

Note: you can execute the same operation on a specific list with the same schema presented before.

You can query all the columns associated to a specific content type, you need to replace {siteId} and {contentTypeId} in the query:

GET https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}/columns

JSON response body:

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#sites('<SHAREPOINT_TENANT_ID>')/contentTypes('0x01004DFD4872F860FD43AB3F75D69CF6141E')/columns",
    "value": [
        {
            "@odata.etag": "\"0\"",
            "columnGroup": "_Hidden",
            "description": "",
            "displayName": "Content Type",
            "enforceUniqueValues": false,
            "hidden": false,
            "id": "c042a256-787d-4a6f-8a8a-cf6ab767f12d",
            "indexed": false,
            "isDeletable": false,
            "isReorderable": false,
            "isSealed": true,
            "propagateChanges": false,
            "name": "ContentType",
            "readOnly": false,
            "required": false,
            "type": "unknownFutureValue"
        },
        {
            "@odata.etag": "\"0\"",
            "columnGroup": "_Hidden",
            "description": "",
            "displayName": "Title",
            "enforceUniqueValues": false,
            "hidden": false,
            "id": "fa564e0f-0c70-4ab9-b863-0177e6ddd247",
            "indexed": false,
            "isDeletable": false,
            "isReorderable": true,
            "isSealed": false,
            "propagateChanges": false,
            "name": "Title",
            "readOnly": false,
            "required": true,
            "type": "text",
            "text": {
                "allowMultipleLines": false,
                "appendChangesToExistingText": false,
                "linesForEditing": 0,
                "maxLength": 255
            },
            "validation": {
                "defaultLanguage": "en-US",
                "descriptions": [
                    {
                        "languageTag": "en-US"
                    }
                ]
            }
        },
        {
            "@odata.etag": "\"0\"",
            "columnGroup": "Custom Columns",
            "description": "",
            "displayName": "Test Col",
            "enforceUniqueValues": false,
            "hidden": false,
            "id": "ed4892ba-d132-4f96-b157-e86d4f98d6be",
            "indexed": false,
            "isDeletable": true,
            "isReorderable": true,
            "isSealed": false,
            "propagateChanges": false,
            "name": "Test_x0020_Col",
            "readOnly": false,
            "required": true,
            "type": "boolean",
            "boolean": {},
            "defaultValue": {
                "value": "0"
            }
        }
    ]
}

Note: you can execute the same operation on a specific list with the same schema presented before.

Or you can query a specific column metadatas associated to a specific content type, you need to replace {siteId}, {contentTypeId} and {columnId} in the query:

GET https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}/columns/{columnId}

JSON response body:

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#sites('<SHAREPOINT_TENANT_ID>')/contentTypes('0x01004DFD4872F860FD43AB3F75D69CF6141E')/columns/$entity",
    "@odata.etag": "\"0\"",
    "columnGroup": "Custom Columns",
    "description": "",
    "displayName": "Test Col",
    "enforceUniqueValues": false,
    "hidden": false,
    "id": "ed4892ba-d132-4f96-b157-e86d4f98d6be",
    "indexed": false,
    "isDeletable": true,
    "isReorderable": true,
    "isSealed": false,
    "propagateChanges": false,
    "name": "Test_x0020_Col",
    "readOnly": false,
    "required": true,
    "type": "boolean",
    "boolean": {},
    "defaultValue": {
        "value": "0"
    }
}

Note: you can execute the same operation on a specific list with the same schema presented before.

Now let’s see how to create a brand new content type in a specific site, you need to replace {siteId} in the query:

POST https://graph.microsoft.com/beta/sites/{siteId}/contentTypes

JSON request body:

{
    "isBuiltIn": false,
    "description": "This is a demo content type.",
    "group": "Custom Content Types",
    "hidden": false,
    "name": "New Content Type",
    "parentId": "0x01",
    "readOnly": true,
    "sealed": false,
    "base": {
        "id": "0x01",
        "description": "Create a new list item.",
        "group": "List Content Types",
        "hidden": false,
        "name": "Item",
        "readOnly": false,
        "sealed": false
    }
}

Note: creating a new content type is not possible in a specific list context.

Finally we can delete a content type from a specific site, you need to replace {siteId} and {contentTypeId} in the query:

DELETE https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}

Response status will output as below:

HTTP/1.1 204 No Content

Note: you can execute the same operation on a specific list with the same schema presented before.

Publish in hub sites

Microsoft Graph Content types APIs provide capabilities to manage content types publishing in hub sites (for example contentTypeHub site), here are some basic examples of use:

POST https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}/publish

POST https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}/unpublish

POST https://graph.microsoft.com/beta/sites/{siteId}/contentTypes/{contentTypeId}/isPublished

So it was an overview of basic operations with Microsoft Graph Content type APIs and like my previous article on Taxonomy APIs, another article is coming soon to cover advanced scenarios like hub sites management and associations (SharePoint Syntex) and use cases to understand better these great tools.

Common use cases

Like Taxonomy APIs, the common use cases here are for line of business applications which need to perform CRUD operations on tenant sites content types. With the content services APIs it’s now more easy to sync or migrate your third party content management systems metadatas to Microsoft 365 echosystem (for ex. Drupal, Joomla, WordPress, IBM, SAP, Talend, third party MDM, etc.) and I’m sure these APIs will evolve with the upcoming Project Cortex products.

Happy coding everyone!

Resources

https://developer.microsoft.com/en-us/graph

https://docs.microsoft.com/en-us/graph/api/resources/contenttype?view=graph-rest-beta

https://docs.microsoft.com/en-us/graph/api/site-post-contenttypes?view=graph-rest-beta

https://docs.microsoft.com/en-us/graph/api/contenttype-get?view=graph-rest-beta

https://docs.microsoft.com/en-us/graph/api/contenttype-update?view=graph-rest-beta

https://docs.microsoft.com/en-us/graph/api/contenttype-delete?view=graph-rest-beta

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