EWS extended properties in Microsoft Graph
Applications use extended properties to tag items with their own data, and to reach MAPI properties EWS has no name for. Microsoft Graph keeps both uses, with a different way of naming the property and a few gaps.
Naming the property
In EWS a property is an ExtendedPropertyDefinition object. In Graph it is one string, the id, made of the type and the same parts the definition had:
| EWS Managed API | Graph id |
|---|---|
new ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "TicketId", MapiPropertyType.String) | String {00020329-0000-0000-c000-000000000046} Name TicketId |
new ExtendedPropertyDefinition(myGuid, "TicketId", MapiPropertyType.String) | String {myGuid} Name TicketId |
new ExtendedPropertyDefinition(myGuid, 0x8012, MapiPropertyType.Integer) | Integer {myGuid} Id 0x8012 |
new ExtendedPropertyDefinition(0x0E08, MapiPropertyType.Integer) | Integer 0x0E08 |
The GUID in the first row is the public strings property set, which DefaultExtendedPropertySet.PublicStrings stands for. Single values are singleValueExtendedProperties; array types such as StringArray are multiValueExtendedProperties.
Reading
Graph returns an extended property only when the request names it. Microsoft’s documentation says there is no call that returns all extended properties of an item.
GET /users/{user}/messages/{id}?$expand=singleValueExtendedProperties(
$filter=id eq 'String {00020329-0000-0000-c000-000000000046} Name TicketId')Every value comes back as a string, whatever its type: a number as digits, a date in ISO 8601, binary data as base64. The EWS Managed API converted the values for you; with Graph your code has to.
Writing
Include the property in the body when you create or update the item:
PATCH /users/{user}/messages/{id}
Content-Type: application/json
{
"singleValueExtendedProperties": [
{ "id": "String {00020329-0000-0000-c000-000000000046} Name TicketId", "value": "T-10482" }
]
}Finding items by a property
GET /users/{user}/mailFolders/inbox/messages?$filter=singleValueExtendedProperties/Any(
ep: ep/id eq 'String {00020329-0000-0000-c000-000000000046} Name TicketId' and ep/value eq 'T-10482')This covers the common case, an IsEqualTo restriction on a string. Microsoft documents eq, ne, contains and startswith for strings. A value of another type has to be cast before it is compared, for example cast(ep/value, Edm.Int32) gt 5, and then the usual comparison operators apply. The name inside the ID is case-sensitive; the comparison of string values is not.
The rest of what EWS restrictions could do with extended properties is narrower in Graph. We measured these on Exchange Online:
- An
Existsrestriction needs a condition on the value.ep/id eq '...' and ep/value ne nullworks. - A binary property cannot be used in a filter. To find a meeting by its global object ID, for example, you need another way in.
- Bit masks (
Bitmask) have no counterpart. - Results cannot be sorted by an extended property.
- When the request also has
$orderby, the usual Graph rule applies: the sort properties come first in the filter.
For those cases the code has to read the folder page by page and decide for itself, which is slow in a large folder.
Where extended properties are missing
- Tasks. Graph reaches tasks through Microsoft To Do, and a To Do task has no extended properties. An application that tagged tasks with its own properties cannot read them back through that API.
- Results of
$search. They ignore$expand, so the properties take a second request by ID. - Contact groups, which have no API of their own in Graph v1.0.
What Sunsetless EWS does
With Sunsetless EWS the code keeps its ExtendedPropertyDefinition objects, property sets and restrictions. The library builds the Graph IDs, converts the values back to their types, and evaluates the restrictions and sort orders Graph does not have in folders of up to 20,000 items. For tasks it reads and writes extended properties through the mailbox import and export API. The compatibility table has the details.