Getting or Setting Multi-Value Metadata Fields with the REST API

A couple of years ago, I published a series of posts on getting and setting taxonomy field values in SharePoint workflows by using the REST API in custom workflow activities. These custom activities have served me well, but they've always been unable to work with multi-value taxonomy fields. It's time to fix that.

Background

As you probably already know, when you add a taxonomy field to a SharePoint list, SharePoint adds a corresponding hidden note field. For example, if you add a taxonomy field named Colours, you actually get:

  • A taxonomy field named Colours. Depending on whether the column allows the user to select multiple terms, the field accepts values of type SP.Taxonomy.TaxonomyFieldValue or Collection(SP.Taxonomy.TaxonomyFieldValue).
  • A hidden note field, probably named Colours_0
The hidden note field stores the contents of the field in a term string format. In a single-value taxonomy field, the format looks like this:

Red|87999a76-e3cb-433c-96ad-c6fe354db476

And in a multi-value taxonomy field, the format looks like this:

Blue|77788fee-9e1d-4df2-a21b-d41dd1734b71;Indigo|d631d196-6909-4b54-a8bb-3c15bcfec18a;Yellow|fdc643a8-3310-448d-9a5d-c9ba13f366fb

In other words, each managed metadata term is represented as [Label]|[Term GUID], and multiple terms are separated by semi-colons.

I've previously resisted using this hidden note field to work with taxonomy fields using REST, as I've always felt that you ought to be able to get or set the field value using the regular, visible taxonomy field. You can get or set single values using the regular taxonomy field, so why not multiple values? However, I eventually conceded it can't be done. Meanwhile, other bloggers, such as this excellent post from Beau Cameron, have demonstrated how you can update a multi-value taxonomy field over REST by targeting the hidden note field and providing a text value in the term string format shown above.

The Problem

We know we can set single-value or multi-value taxonomy fields by writing a text value to the hidden note field. However, if we want to automate this (for example in a custom workflow activity), we need a reliable way of identifying the hidden note field that corresponds to a particular taxonomy field. This is tricky:
  • The internal/static name of the note field is typically a randomly-generated GUID.
  • You can't assume that the title of the note field will always be the title of the taxonomy field with "_0" appended - it depends how the field was created.
Fortunately there is a more robust way of finding the hidden note field associated with a specific taxonomy field.

The Solution - Short Version

If you retrieve the properties of a taxonomy field in any list, you'll notice it has a property named TextField. This field stores the GUID identifier of the associated hidden note field. Given this identifier you can retrieve the hidden note field (reliably and programmatically) and get its internal name. Given the internal name of the hidden note field, you can update your taxonomy field by providing a string value.

Update 1st June 2017: There's a nasty little gotcha to be aware of if you use this approach with SharePoint Online (doesn't seem to affect SharePoint on-premises). If the name of the taxonomy field includes spaces, you might see one of SharePoint's legendary errors from hell when you try to write to the hidden note field: "One or more field types are not installed properly. Go to the list settings page to delete these fields." This is pretty frustrating - the internal name of the hidden note field is a GUID and you can grab this reliably every time, but it seems that in these scenarios SharePoint is unable to internally reconcile the hidden note field and the visible taxonomy field. To avoid this issue, make sure your field names don't include spaces at the point of creation. (If you create your fields declaratively or programmatically, you can of course have a space-free internal name and a display name with as many spaces as you like.)

The Solution - Long Version

First, send a REST request to retrieve the details of the taxonomy field:


[Site URL]_api/web/lists(guid'[List ID]')/fields?$filter=title eq '[Field Name]' or internalname eq '[FieldName]'

For example, suppose we've got a multi-value taxonomy field named Colours:

GET /_api/web/lists(guid'd190f637-2f62-41e8-b191-bf760daff64f')/fields?$filter=title eq 'Colours' or internalname eq 'Colours' HTTP/1.1
Accept: application/json; odata=verbose
Host: sp.jrjlee.net

The REST API will return a response that resembles the following:

{
    "d": {
        "results": [
            {
                "__metadata": {...},
                "DescriptionResource": {...},
                "TitleResource": {...},
                "AutoIndexed": false,
                "CanBeDeleted": true,
                "DefaultValue": "",
                "Description": "",
                "Direction": "none",
                "EnforceUniqueValues": false,
                "EntityPropertyName": "Colours",
                "Filterable": true,
                "FromBaseType": false,
                "Group": "Jason Columns",
                "Hidden": false,
                "Id": "5e198f9b-6daf-4c13-ad01-cdb616a06ab4",
                "Indexed": false,
                "InternalName": "Colours",
                "JSLink": "...",
                "ReadOnlyField": false,
                "Required": false,
                "SchemaXml": "...",
                "Scope": "/Lists/Things",
                "Sealed": false,
                "Sortable": false,
                "StaticName": "Colours",
                "Title": "Colours",
                "FieldTypeKind": 0,
                "TypeAsString": "TaxonomyFieldTypeMulti",
                "TypeDisplayName": "Managed Metadata",
                "TypeShortDescription": "Managed Metadata",
                "ValidationFormula": null,
                "ValidationMessage": null,
                "AllowMultipleValues": true,
                "IsRelationship": true,
                "LookupField": "Term$Resources:core,Language;",
                "LookupList": "{567ae30f-ee2d-4d07-8c4a-a6467a94959c}",
                "LookupWebId": "9361373e-19a2-42e5-bf36-a67adfeae11e",
                "PrimaryFieldId": null,
                "RelationshipDeleteBehavior": 0,
                "AnchorId": "00000000-0000-0000-0000-000000000000",
                "CreateValuesInEditForm": false,
                "IsAnchorValid": true,
                "IsKeyword": false,
                "IsPathRendered": false,
                "IsTermSetValid": true,
                "Open": false,
                "SspId": "8750d12e-49d8-4326-84a6-ae8bd5a953c6",
                "TargetTemplate": null,
                "TermSetId": "ba00c0bf-3de6-45d7-96b3-c23debc868c4",
                "TextField": "451e34e4-c9b6-43d9-8499-e0495c6dcb4a",
                "UserCreated": false
            }
        ]
    }
}

The TextField property is the ID of the hidden note field that is associated with this taxonomy field. If you're building a workflow activity, you can pull the value out of the response using the XPath query d/results(0)/TextField.

Once you've got the field ID, you can send another REST request - this time to retrieve the details of the hidden note field:

[Site URL]_api/web/lists(guid'[List ID]')/fields(guid'[Field ID]')

For example:

GET /_api/web/lists(guid'd190f637-2f62-41e8-b191-bf760daff64f')/fields(guid'451e34e4-c9b6-43d9-8499-e0495c6dcb4a') HTTP/1.1
Accept: application/json; odata=verbose
Host: sp.jrjlee.net


As before, the REST API returns a response that resembles the following:

{
    "d": {
        "__metadata": {...},
        "DescriptionResource": {...},
        "TitleResource": {...},
        "AutoIndexed": false,
        "CanBeDeleted": true,
        "DefaultValue": null,
        "Description": "",
        "Direction": "none",
        "EnforceUniqueValues": false,
        "EntityPropertyName": "le198f9b6daf4c13ad01cdb616a06ab4",
        "Filterable": false,
        "FromBaseType": false,
        "Group": "Jason Columns",
        "Hidden": true,
        "Id": "451e34e4-c9b6-43d9-8499-e0495c6dcb4a",
        "Indexed": false,
        "InternalName": "le198f9b6daf4c13ad01cdb616a06ab4",
        "JSLink": "clienttemplates.js",
        "ReadOnlyField": false,
        "Required": false,
        "SchemaXml": "...",
        "Scope": "/Lists/Things",
        "Sealed": false,
        "Sortable": false,
        "StaticName": "le198f9b6daf4c13ad01cdb616a06ab4",
        "Title": "Colours_0",
        "FieldTypeKind": 3,
        "TypeAsString": "Note",
        "TypeDisplayName": "Multiple lines of text",
        "TypeShortDescription": "Multiple lines of text",
        "ValidationFormula": null,
        "ValidationMessage": null,
        "AllowHyperlink": false,
        "AppendOnly": false,
        "NumberOfLines": 6,
        "RestrictedMode": true,
        "RichText": false,
        "WikiLinking": false
    }
}

This time, we grab the InternalName property (d/InternalName) from the response.

Now that we've got the internal name of the hidden note field, we can use the REST API to get or set taxonomy values programmatically. We send a MERGE request:

POST /_api/web/lists(guid'd190f637-2f62-41e8-b191-bf760daff64f')/Items(12) HTTP/1.1
If-Match: *
X-HTTP-Method: MERGE
Content-Type: application/json; odata=verbose
Host: sp.jrjlee.net

And in the JSON body of the request we set our hidden field (using the GUID-based internal name) to our multi-value term string:

{
    "le198f9b6daf4c13ad01cdb616a06ab4": "Red|87999a76-e3cb-433c-96ad-c6fe354db476;Blue|77788fee-9e1d-4df2-a21b-d41dd1734b71;Violet|a1a70ca3-b104-49f7-86c9-38a265f35f4d",
    "__metadata": { "type": "SP.Data.ThingsListItem" }
}

The end result? Our Colours taxonomy field shows the new values as expected.
























Comments

  1. Hey Jason! Great Post, I appreciate the link to my blog as well! I'm glad you weren't the only one who felt all of the complications with Taxonomy Fields and REST. :)

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete

Post a Comment

Popular posts from this blog

Server-side activities have been updated

The target principal name is incorrect. Cannot generate SSPI context.

Custom Workflow Activity for Creating a SharePoint Site