This article describes in detail how to migrate the relationship types of your legacy custom objects to the new custom objects experience.

In database design, a relationship type describes how two or more tables are associated with each other. The relationship defines how data in one table relates to data in another. For example, a record in one table can be related to many records in a second table.

Similarly in custom objects, a relationship type defines how two object records are associated with each other. For example, a record of one object type can be related to many records of a second object type.

Relationship types don't actually create relationships between existing records. In legacy custom objects, a relationship between two existing records is created with a relationship record. The relationship records of legacy custom objects are migrated to the new custom objects experience separately. See Step 5: Migrating the relationship records of legacy custom objects.

Topics covered in this article:

This is the third article in a series on migrating legacy custom objects:

Disclaimer: Zendesk provides this article for informational purposes only. Zendesk doesn't provide support for the code in this article, nor does Zendesk support third-party technologies such as Python.

Understanding relationship types in legacy custom objects

In legacy custom objects, a relationship type is defined by an object with a key, source, and target property. Example:

{      "data": {        "key": "product_has_many_users",        "source": "product",        "target": ["zen:user"]    }}

The source and target values can be any custom object or a supported standard Zendesk object such as "zen:ticket" or "zen:user".

The square brackets define the cardinality of the relationship -- one-to-one, one-to-many (or many-to-one), or many-to-many. The brackets can enclose the target value for one-to-many relationships, each value for many-to-many relationships, or neither value for one-to-one relationships. For more information, see Legacy relationship types.

Understanding relationships in the new custom objects experience

In the new custom objects experience, a relationship is defined by a lookup relationship field.

A lookup relationship field always defines a "many-to-one" relationship:

  • The "many" object is always the object that contains the lookup relationship field.
  • The "one" object is always the object that populates the options in the lookup relationship field's drop-down menu. The options are records of the "one" object.

The reason lookup relationship fields define many-to-one relationships is that the field only allows you to pick one record from the lookup field for each record of the "many" object. Over time, more than one record of the "many" object can become related to a particular record of the "one" object. Or stated in a different way, a single record of the "one" object can become related to more than one records of the "many" object.

You can add one or more lookup relationship fields to the following pages in Zendesk:

  • The ticket page or the support request form in your help center
  • The user profile page
  • The organization page

The objects that these pages represent -- the standard Zendesk ticket, user, and organization objects -- automatically become the "many" object of the relationship.

The following example shows the body of an API request that adds a lookup relationship field to the user profile page in Zendesk (which is indicated by the user_field property). The Zendesk user object is the "many" object in the relationship and the "rental_property" custom object is the "one" object.

{    "user_field": {        "key": "users_who_booked_a_rental_property",        "title": "Users who booked a rental property",        "type": "lookup",         "relationship_target_type": "zen:custom_object:rental_property"    }}

As well as adding lookup relationship fields to the pages of standard objects in Zendesk, you can also add lookup fields to custom objects. As the object that contains the lookup field, the custom object automatically becomes the "many" object.

In the following example, the lookup field was added to a "rental_property" custom object. The "many" object is the "rental_property" custom object and the "one" object is the Zendesk user object.

{    "custom_object_field": {        "key": "rental_properties_booked_by_user",        "title": "Rental properties booked by user",        "type": "lookup",        "relationship_target_type": "zen:user"    }}

Limitations

Standard Zendesk objects

Lookup relationship fields don't support all the standard Zendesk objects that relationships in legacy custom objects do. If the source or target of a legacy relationship is a standard Zendesk object that's not supported in the new custom objects experience, then you won't be able to migrate the relationship.

The following standard objects are supported in both the legacy and new custom objects experiences:

  • "zen:ticket"
  • "zen:user"
  • "zen:organization"

The following standard objects are supported in the legacy custom experience but are not supported in the new custom objects experience:

  • "zen:group"
  • "zen:article"
  • "zen:chat"
  • "zen:brand"
  • "zen:lead"
  • "zen:contact"
  • "zen:deal"

Relationship cardinality

Lookup relationship fields only support many-to-one or one-to-many relationships.

However, you can create a many-to-many relationship using a junction object. For more information, see Creating many-to-many relationships in Zendesk using custom objects.

Migrating legacy relationship types

Migrating legacy relationship types consists of adding a lookup field to the "many" object of each relationship. The "many" object could be a standard Zendesk object or a migrated custom object.

For example, suppose the legacy relationship type defines the following one-to-many relationship:

{      "data": {        "key": "product_has_many_users",        "source": "product",        "target": ["zen:user"]    }}

Add the lookup field to the specified by the target object -- the Zendesk user object. Adding the field to the user object makes it the "many" object of the lookup field.

Specify the source object of the relationship type as the relationship_target_type of the lookup field. This makes the "rental_property" object the "one" object of the lookup field.

Example request body:

{    "user_field": {        "key": "product_has_many_users",        "title": "Product users",        "type": "lookup",         "relationship_target_type": "zen:custom_object:product"    }}

If the "many" target of a relationship type is a custom object, then add a lookup custom object field to the migrated custom object. In the following example, the target object of the relationship type is a custom object:

{      "data": {        "key": "user_booked_many_rental_properties",        "source": "zen:user",        "target": ["rental_property"]    }}

Add the lookup field to the migrated "rental_property" custom object and specify the user object as the relationship_target_type of the lookup field. The "rental_property" object becomes the "many" object and the user object becomes the "one" object of the lookup field.

Example lookup field:

"custom_object_field": {    "key": "user_booked_many_rental_properties",    "title": "Rental properties booked by user",    "type": "lookup",    "relationship_target_type": "zen:user"}

Example main function logic

The following example main() function shows the general logic of migrating legacy relationship types to the new custom objects experience. The called functions are explained in the follow-up sections.

def main():    lookup_field_map = {}
    relationship_types = get_relationship_types()    for relationship_type in relationship_types:        if relationship_type['target'][:4] == 'zen:':            add_lookup_field_to_zen_object(relationship_type, lookup_field_map)        else:            add_lookup_field_to_custom_object(relationship_type)
    write_json_file('lookup_field_map.json', lookup_field_map)

# utility functiondef write_json_file(file_path, file_data):    with open(file_path, mode='w', encoding='utf-8') as f:        json.dump(file_data, f, sort_keys=True, indent=2)

Example function for getting the relationship types

The following example Python function gets the relationships types and filters out any unsupported ones.

def get_relationship_types():    endpoint = f'api/sunshine/relationships/types'    url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'    auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN    response = requests.get(url, auth=auth)    relationship_types = response.json()['data']
    supported_rel_types = []    for relationship_type in relationship_types:        if relationship_type_is_not_supported(relationship_type):            continue        supported_rel_types.append(relationship_type)    return supported_rel_types

def relationship_type_is_not_supported(relationship_type) -> bool:    """    Checks for unsupported relationship cardinality or unsupported Zendesk standard objects    """    # check for many-to-many relationship    if (isinstance(relationship_type['source'], list)            and isinstance(relationship_type['target'], list)):        return True
    # check for one-to-one relationship    if (not isinstance(relationship_type['source'], list)            and not isinstance(relationship_type['target'], list)):        return True
    # check for unsupported Zendesk standard objects    if (relationship_type['source'][:4] == 'zen:'            and relationship_type['source'] not in ['zen:ticket', 'zen:user', 'zen:organization']):        return True
    if (relationship_type['target'][:4] == 'zen:'            and relationship_type['target'] not in ['zen:ticket', 'zen:user', 'zen:organization']):        return True
    return False

Example function for adding a lookup field to a Zendesk object

The following example Python function adds a lookup relationship field to a Zendesk object.

The API endpoint the function uses to add the field depends on the Zendesk object:

def add_lookup_field_to_zen_object(relationship_type, lookup_field_map):    relationship_target_type = f'zen:custom_object:{relationship_type['source']}'    lookup_relationship_field = {        'title': relationship_type['key'].replace('_', ' ').capitalize(),        'type': 'lookup',        'relationship_target_type': relationship_target_type    }    standard_object_page = relationship_type['target'][0].split(':')[1]    data = {f'{standard_object_page}_field': lookup_relationship_field}    endpoint = f'api/v2/{standard_object_page}_fields'
    # make the request    url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'    auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN    response = requests.post(url, json=data, auth=auth)    if response.status_code != 201:        print(f'-- {response.status_code}: {response.text}')        return {}
    # update lookup field map    data = response.json()    lookup_field_id = data['ticket_field']['id']    lookup_field_map[relationship_type['key']] = lookup_field_id    return response

Example function for adding a lookup field to a custom object

The following example Python function adds a lookup field to a custom object. It uses the Create Custom Object Field endpoint to add the field.

def add_lookup_field_to_custom_object(relationship_type):    custom_object_field = {        'key': relationship_type['key'],        'title': relationship_type['key'].replace('_', ' ').capitalize(),        'type': 'lookup',        'relationship_target_type': relationship_type['source']    }    custom_object_key = relationship_type['target'][0]    data = {'custom_object_field': custom_object_field}    endpoint = f'api/v2/custom_objects/{custom_object_key}/fields'
    # make the request    url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'    auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN    response = requests.post(url, json=data, auth=auth)    if response.status_code != 201:        print(f'-- {response.status_code}: {response.text}')        return {}

Next steps

After migrating your legacy custom object types, schemas, and relationship types, you can migrate the object records. See Step 4: Migrating the records of legacy custom objects.