# SDK Development Best Practices
SUMMARY
- Keep code clean and understandable, and be expressive in naming.
- Use secure authentication and user-friendly fields, and don't include sensitive data in code.
- Use minimal, low-privilege endpoints for tests.
- Prefer dynamic object definitions over static, include user-friendly labels, and use
control_type
judiciously.
Below, we have compiled a list of best practices which makes development of your custom connector easier to build, test, and maintain. We have organized this best practices in the following way:
- General best practices
- Security best practices
- Root key-specific best practices
- Action best practices
- Trigger best practices
- Usability and testing best practices
# General
These best practices relate directly to the development of a custom connector on Workato's SDK platform.
The connector should be named after the application. This makes it easier for you or collaborators in your workspace to search for your own custom connector
- If a standard/global connector already exists, the SDK should be named
<app name> (Custom)
, which indicates it’s custom SDK connector
- If a standard/global connector already exists, the SDK should be named
Provide trigger and action level hints when building actions. This allows users who aren't familiar with your connector understand which action to choose
Avoid leaving unused variables, methods, pick lists and object_definitions inside your connector code
Keep the code clean, easy to read, understand, and extensible
- Follow the DRY principle of Ruby, use methods and object_definitions for reusable code liberally
Always be expressive when declaring named objects or methods
- Use the snake case naming convention for declaring actions, triggers, pick lists, methods, and variables
- Do not use short codes, special chars for declaring variables
- Users should be able to understand what each block of code is trying to accomplish based on its name
- Include comments before Actions and Triggers, indicate what it does and any special instructions and limitations
Include empty lines between each key (methods, actions, triggers, pick lists etc.). This makes your code more readable for those looking to improve upon it
Use the
dig
method when you need to navigate data to two or more levels. Learn more (opens new window)Use
#{}
instead of string concatenation ("string" + "string"
) whenever possible#{}
is more defensive as errors are not raised when the variable is not declared
Use Date fields and format cautiously, ensure the time zones are handled
Avoid puts inside the code except for when testing and debugging using the
Console
tab in the debugger consoleAvoid implementing triggers(strictly) and actions for endpoints with HTTP Rate limits
- If the action is required, then handle rate limit logic on the recipe but not in the connector code
Each Trigger and Action should have a description tag with appropriate action or trigger name
- Standard convention:
<action/trigger> <object> in <applicationName>
- Example: Search invoices in Xero
- Standard convention:
# Security
- Perform validations in the
execute
lambda that are necessary to safeguard your end user's target application. Expect that users may map data from applications such as Slack, Teams, or external forms and there may be attempts to perform injection or path traversal attacks.- If you control the application, ensure that the applications you work with handle these inputs by sanitizing inputs received from API requests. API providers should understand more deeply the situations where specific inputs are sensitive to malicious user input.
- On the Workato end, ensure you make additional attempts to prevent malicious input by validating inputs.
- Advise your end users to avoid providing free-form user input into sensitive fields. For example, specifying dropdowns of enumerated values where possible.
# Root Key-specific
# Connection
Use control_type: password for sensitive data
For OAuth 2.0 connections that allow you to request for scopes in the authorization URL, this should be an input that users of your connector can select when creating a connection.
Use control_type: subdomain and url when requesting user input about a subdomain parameter
- This makes your input fields more usable to end users and minimizes the amount of human error possible
- Example:
fields: lambda do |_connection, _config_fields| { name: 'sub_domain', control_type: 'subdomain', url: '.salesforce.com', optional: false, hint: 'Provide salesforce sub-domain for example, <code>test_instance</code>' } end
If the connector is intended for distribution, ensure that no sensitive details are kept inside the code
- This is especially important for Client IDs and Secrets when authenticating through an OAuth 2.0 flow
Define refresh_token and detect_on keys for authorization tokens that expires over time. While your initial connection may be successful, your connection may break when the token expires
Provide reference links to aid users into how to attain credentials to create a successful connection
- A simple thing like adding a link to the URL that lets you generate API keys or client IDs and secrets are go a long way for users of your custom connector.
Ensure required scopes are included in the authorization URL for OAuth 2.0 authentication
When using
type: "custom_auth"
, theacquire
key is only run if thedetect_on
orrefresh_on
is triggered. When using theacquire
key to retrieve credentials like tokens, be sure to include the error code that is returned when yourtest
key is executed without retrieving the proper tokens.Include version of the API if the app supports multiple versions at the same time
Store the API url in connection hash
- If the base url is dynamic and tenant specific, use acquire key to fetch the base_url dynamically
Use picklists to select production, sandbox environments if the SDK need to support different environments
Use picklists in the
fields
key in theconnection
key to avoid typos, which leads to connection failuresUse base_uri(when applicable) to set the base url for API calls, which avoids keeping the full URL in triggers, methods, and picklists
- Example:
base_uri: lambda do |connection| if connection['custom_domain'] "https://#{connection['custom_domain']}" else 'https://go.trackvia.com' end end
Use the static base_uri or acquire the base_url from the endpoint (if there is an API which returns base_url account specific)
# Test
Use endpoint with least privileges and minimum data in the response for testing the connection.
- For example, use endpoint “get(/profile/me”), which may hold min. data in the memory.
Minimize the get method to store least possible data on the test endpoint call.
- This reduces the amount of time to successfully create a connection
- Validate connection status before a recipe is started
# Object Definitions
Dynamic object definitions should be preferred over static object definitions
- Dynamic object definitions reduce the amount of maintenance of the custom connector - especially when users can create custom fields in the application which you want to connect to
- Dynamic object definitions normally depend on sending HTTP requests to metadata endpoints of the App you hope to connect to. Use the response to transform the data into the format expected in the
input_fields
andoutput_fields
keys.
Ensure that your object definitions are always defined with arguments, even if they are not used. This prevents any backwards compatibility issues in the future.
Special characters are not allowed in object field names except underscore
_
Use labels to increase the usability for custom connector end users
- For example, some APIs provide metadata endpoints whose responses come with suitable labels for fields. For example, metadata about fields in an target application may contain an
ID
field as well as aname
field. This may require us to mapname
in our object definitions toID
andlabel
in our object definitions toname
to maximize usability for end users.
- For example, some APIs provide metadata endpoints whose responses come with suitable labels for fields. For example, metadata about fields in an target application may contain an
Use
control_type
judiciously to reduce user error- For example, use
control_type
=date_time
instead oftext
when looking to collect date time input
- For example, use
Toggle fields should be used for
boolean
,select
,multi-select
control_types to allow users to toggle totext
input to map datapills during recipe design timeWhen Toggle option is provided, toggle hints should indicate allowed values.
- List values on the recipe UI if only few values, otherwise link to the appropriate page to show possible values for the toggle field
Use number type when you need double and float, currency values.
Use static pick_list values for select options if the options are static for example, Genders, Address Type, Currency types
# Actions
Actions should be clearly named
- Naming conventions for actions:
- Get - Get only one specific record by ID
- Search - Return 0, 1, or more records based on a search query
- List - List out all records
- Add - Create a new record
- Create - Create a new record
- Update - Update an existing record
- Upsert - Create a new record or update an existing record
- Naming conventions for actions:
Be sure to perform validations on input_fields whenever necessary
- We always advise guarding against edge cases by performing validation
Use
help
tag, to indicate any special instructions to the userIn execute block, call target application endpoints only for the data
- Metadata HTTP requests should have been executed in
object_definitions
- Metadata HTTP requests should have been executed in
Use methods as much as possible to reduce redundant code
Use
after_response
block to capture response header information like cookies etc.It’s good practice to have a custom action in connector for CRUD operations, which can be used for any endpoint
Actions that delete entire tables or impact object_definitions are not advisable in Workato
- These actions have lasting impacts and potentially lead to data loss
- It is advised for these actions to be deliberate and done directly by the an admin on the application instead of through a recipe
# Triggers
# General
Name of the trigger should be specific to what it does
For example, New employee in Replicon - Triggers when new employee created in Replicon
Naming conventions for Triggers:
- New - Trigger that detects when objects are created
- New or updated - Trigger that detects when objects are either created or updated
- Deleted - Trigger that detects when objects are deleted
Since
input fields are often useful for users to retrieve data from the past- This could be an optional field to allow users the ability to pull records from a past date when first starting the recipe
- Traditionally, APIs should support this by allowing you to query records since a past date using a set parameter
Avoid making unnecessary API requests in the poll key as this key is executed at least once in each trigger poll
Use closure key to store query fields, page number, last modified date time (only if required)
- Information cached in closure is persisted across poll intervals.
Use methods as much as possible to reduce redundant code
Dynamic webhooks should be used in APIs that have functionality for programmatically setting up and tearing down of webhook URLs
- Static webhooks are the alternative but require you to manually register Workato's given static URL
# Sample Output
In Workato recipes, every action or trigger should have sample output data populated with output data pills under app data section
- This gives user idea about the data that he uses in downstream systems and improves usability
Static sample output data is preferred for the objects with fewer fields
- Dynamic sample outputs can be used for objects with large amounts of fields
- Avoid too much of data transformation and too many API calls to show sample data in the
sample_output
key - For download files actions, use static data in
sample_output
key
Ensure the sample data is populated for each trigger or action(output)
- This should show up as grey text next to each datapill
- When triggers do not have any input fields, the datatree does not show up until a second action is added
# Error Handling
Signal exceptions using the raise method
Catch validation errors early, instead of waiting for API to return errors.
Implement Error handling when you need to handle specific error codes in the SDK and define your own response
Don’t suppress exceptions, better to expose more API information than hide them
# Usability And Testing
# General
Check the Recipe UI for actions and triggers
Ensure the action names, triggers names, labels, and help instructions clearly communicate their purpose to the end user
Remember to set up some end to end tests for your custom connector by creating recipes
- This is especially useful for pushing new versions of your connector to your production workspace by first testing it in recipes using your sandbox environment
# Usability Rules
Now that you’ve learned some concepts behind creating object-based actions and triggers, you should now begin testing it not only in the debugger console but when used as a recipe. When you reach this stage, you may also start to take note of certain aspects of your connector which are not as usable as you would hope. Good connectors are not only well organized in terms of code but place user experience front and center in the entire recipe building experience. Here are some rules that distinguish good connectors from those that aren't user-friendly:
# Descriptive Help Text
Help texts in actions are critical in helping your users plug any knowledge gaps they may have about the actions you've built. Here are some important details that you should include in the help texts of your actions:
- Supported API versions
- Object-specific help
- Links to documentation
- Field-level hints
- Field-level help
# Supported API versions
API versions of the application you are connecting to help manage expectations for your users. They would have a better understanding of what to expect in terms of functionality for your connector.
# Object-specific help
Different objects may require different action level help hints. Help texts can be easily changed based on the object users select.
help: lambda do |input, picklist_label|
if input['object'] == 'invoice'
{
body: "Creates an invoice in XYZ. Invoices in XYZ accounting are essential " \
"components of the platform. This action supports the creation of invoices " \
"including custom fields",
learn_more_url: "docs.xyz.com",
learn_more_text: "Learn to setup this action"
}
else
{
body: "Creates an object in XYZ. First, select from a list of " \
'objects that we currently support. After selecting your object,' \
' dynamic input fields specific to the object selected ' \
'will be populated.'
}
end
end,
# Links to documentation
Help texts can also include links to appropriate documentation if users need more information about how to set up this action.
Linking to documentation can help your users when not all the information can be contained in a small paragraph
# Field-level hints
Hints are an essential way to guide your users on how to use a specific input field. Let them know about input expected such as whether you require the timestamp to be in a specific date format (iso8601 or DD/MM/YYYY) etc.
# Field-level help
In cases where it is critical for your users to read this to configure the action properly, we suggest using field level help. This should be used sparingly.
[
{
control_type: "text",
label: "Txn date",
type: "string",
name: "TxnDate",
optional: true,
sticky: true,
help: {
title: "Extra info",
text: "This field is super important"
}
}
]
Bring attention to a specific field using field level help
# User-Friendly Input Fields
Here are some simple rules that would help fine-tune your connector to make it as user-friendly as possible. When creating connectors with the eventual aim of getting them listed on Workato as globally scope connectors, these rules will form an important part of the UIUX review that we put each connector through.
Is the label of the input field descriptive enough? Be as explicit as possible when defining labels on input fields so your end users are on the same page as you. While the
name
of the field may beid
, changing the label toInvoice ID
makes it immediately clear to end users what they are doing.Is the type and control_type of the input field accurate? Types and control_types help your users know what kind of values they are working with. Workato defaults the type and control_type to string and text if nothing is defined.
Did you declare hints for input fields that might still be ambiguous after changing the label? Using hints are a great way to guide your users even more. Links in HTML format can also be used.
Does this input field only accept a defined set of values? Using select dropdowns make it easier for your users to give correct input instead of typing in their answers manually. This also reduces the number of errors they might face.
Does this input field accept ID values? For example, a
create invoice
action in XYZ accounting might require an input field that contains the ID of the associated customer. Since your users might not have IDs of customers on hand, but the name of the customer instead, you may consider making the input field a picklist which shows customer names as the label but provides the ID of the customer as the value instead.When using dropdowns, always also include a toggle_field option that allows your users to map datapills if they need to. While a dropdown is great, sometimes your users may need to map datapills instead. Remember to change the toggle_hints accordingly.
Is this dropdown a config field? If yes, ensure that the secondary toggle field has
control_type
set toplain-text
to prevent datapills from being mapped. Config fields should never accept datapills as other input fields in the action rely on the information.Are all required fields in the action or trigger labeled as required? Users should be able to quickly understand which fields they need to fill in for this action to be valid.
Are there any optional fields that will be commonly used? Making these fields sticky can bring end-users attention to their input fields instead of having them search for them.
Are you dynamically retrieving possible custom fields for end users of your connector? Use the
custom
parameter in each custom field to provide your users with feedback on which fields are standard and which fields are custom to them.
For each input field, we suggest running through this series of questions quickly. Once you get the hang of it, it becomes a simple process of highlighting input fields which need adjustments before going back into the schema definitions to make changes.
# Descriptive Error Messages
Descriptive error messages are a crucial part of the recipe building experience for end-users. Without the proper error messages, users have a tough time figuring out why their recipes are failing. If you haven’t checked out the possible ways to surface errors on Workato, do check out our error handling guide.
Here are some general rules to include proper error handling in your connector.
Does your connector use picklists or dynamic schema of any sort? Chaining an
after_error_response
function allows your users to receive exact information of what may have gone wrong. Example here.Does your connector have certain fields that are required together, such as a start date and end date? Whilst these fields may not be required all the time, some fields are often required together. In cases like these, validations may help surface these errors better and also reduce the number of API calls made unnecessarily. Example here.
Does the API you are connecting to respond with appropriate HTTP status codes? In certain cases, APIs may send back responses that should actually be errors but have their HTTP status as
200
. In cases like these, using anafter_error_response
function can help highlight issues to your users instead. Example here.
Last updated: 11/17/2023, 7:21:40 AM