Implementing Dynamic Types in Docusign Extension Apps
In our previous blog post about Docusign Extension Apps, Advanced Authentication and Onboarding Workflows with Docusign Extension Apps, we touched on how you can extend the OAuth 2 flow to build a more powerful onboarding flow for your Extension Apps. In this blog post, we will continue explaining more advanced patterns in developing Extension Apps. For that reason, we assume at least basic familiarity with how Extension Apps work and ideally some experience developing them.
To give a brief recap, Docusign Extension Apps are a powerful way to embed custom logic into Docusign agreement workflows. These apps are lightweight services, typically cloud-hosted, that integrate at specific workflow extension points to perform custom actions, such as data validation, participant input collection, or interaction with third-party services. Each Extension App is configured using a manifest file. This manifest defines metadata such as the app's author, support links, and the list of extension points it uses (these are the locations in the workflow where your app's logic will be executed).
The extension points that are relevant for us in the context of this blog post are GetTypeNames
and GetTypeDefinitions
. These are used by Docusign to retrieve the types supported by the Extension App and their definitions, and to show them in the Maestro UI.
In most apps, these types are static and rarely change. However, they don't have to be. They can also be dynamic and change based on certain configurations in the target system that the Extension App is integrating with, or based on the user role assigned to the Maestro administrator on the target system.
Static vs. Dynamic Types
To explain the difference between static and dynamic types, we'll use the example from our previous blog post, where we integrated with an imaginary task management system called TaskVibe. In the example, our Extension App enabled agreement workflows to communicate with TaskVibe, allowing tasks to be read, created, and updated.
Our first approach to implementing the GetTypeNames
and GetTypeDefinitions
endpoints for the TaskVibe Extension App might look like the following. The GetTypeNames
endpoint returns a single record named task
:
{
"typeNames": [
{
"typeName": "task",
"label": "Task",
"description": "A task on TaskVibe."
}
]
}
Given the type name task
, the GetTypeDefinitions
endpoint would return the following definition for that type:
{
"declarations": [
{
// ...
"name": "task",
"isAbstract": false,
"identified": {
"$class": "concerto.metamodel@1.0.0.IdentifiedBy",
"name": "recordId"
},
"properties": [
{
// ...
"name": "recordId"
},
{
// ...
"name": "title"
},
// Other task properties
]
}
]
}
As noted in the Docusign documentation, this endpoint must return a Concerto schema representing the type. For clarity, we've omitted most of the Concerto-specific properties. The above declaration states that we have a task
type, and this type has properties that correspond to task fields in TaskVibe, such as record ID, title, description, assignee, and so on.
The type definition and its properties, as described above, are static and they never change. A TaskVibe task will always have the same properties, and these are essentially set in stone.
Now, imagine a scenario where TaskVibe supports custom properties that are also project-dependent. One project in TaskVibe might follow a typical agile workflow with sprints, and the project manager might want a "Sprint" field in every task within that project. Another project might use a Kanban workflow, where the project manager wants a status field with values like "Backlog," "ToDo," and so on. With static types, we would need to return every possible field from any project as part of the GetTypeDefinitions
response, and this introduces new challenges. For example, we might be dealing with hundreds of custom field types, and showing them in the Maestro UI might be too overwhelming for the Maestro administrator. Or we might be returning fields that are simply not usable by the Maestro administrator because they relate to projects the administrator doesn't have access to in TaskVibe.
With dynamic types, however, we can support this level of customization.
Implementing Dynamic Types
When Docusign sends a request to the GetTypeNames
endpoint and the types are dynamic, the Extension App has a bit more work than before.
As we've mentioned earlier, we can no longer return a generic task type. Instead, we need to look into each of the TaskVibe projects the user has access to, and return the tasks as they are represented under each project, with all the custom fields. (Determining access can usually be done by making a query to a user information endpoint on the target system using the same OAuth 2 token used for other calls.)
Once we find the task definitions on TaskVibe, we then need to return them in the response of GetTypeNames
, where each type corresponds to a task for the given project. This is a big difference from static types, where we would only return a single, generic task.
For example:
{
"typeNames": [
{
"typeName": "task_project1",
"label": "Task - Project 1",
"description": "A task on TaskVibe, project 1."
},
{
"typeName": "task_project2",
"label": "Task - Project 2",
"description": "A task on TaskVibe, project 2."
}
]
}
The key point here is that we are now returning one type per task in a TaskVibe project. You can think of this as having a separate class for each type of task, in object-oriented lingo. The type name can be any string you choose, but it needs to be unique in the list, and it needs to contain the minimum information necessary to be able to distinguish it from other task definitions in the list. In our case, we've decided to form the ID by concatenating the string "task_" with the ID of the project on TaskVibe.
The implementation of the GetTypeDefinitions
endpoint needs to:
- Extract the project ID from the requested type name.
- Using the project ID, retrieve the task definition from TaskVibe for that project. This definition specifies which fields are present on the project's tasks, including all custom fields.
- Once the fields are retrieved, map them to the properties of the Concerto schema.
The resulting JSON could look like this (again, many of the Concerto properties have been omitted for clarity):
{
"declarations": [
{
// ...
"name": "task_project1",
"isAbstract": false,
"identified": {
"$class": "concerto.metamodel@1.0.0.IdentifiedBy",
"name": "project1_task_recordId"
},
"properties": [
{
// ...
"name": "project1_task_recordId"
},
{
// ...
"name": "project1_task_title"
},
{
// ...
"name": "project1_task_spring" // This is a custom property on TaskVibe!
},
// Other task properties
]
}
]
}
Now, type definitions are fully dynamic and project-dependent.
Caching of Type Definitions on Docusign
Docusign maintains a cache of type definitions after an initial connection. This means that changes made to your integration (particularly when using dynamic types) might not be immediately visible in the Maestro UI. To ensure users see the latest data, it's useful to inform them that they may need to refresh their Docusign connection in the App Center UI if new fields are added to their integrated system (like TaskVibe). As an example, a newly added custom field on a TaskVibe project wouldn't be reflected until this refresh occurs.
Conclusion
In this blog post, we've explored how to leverage dynamic types within Docusign Extension Apps to create more flexible integrations with external systems. While static types offer simplicity, they can be constraining when working with external systems that offer a high level of customization. We hope that this blog post provides you with some ideas on how you can tackle similar problems in your Extension Apps.