# SDK Reference - Workato Schema

The section below defines how to define input and output fields in Workato - collectively called "Schema". The information can be applied anywhere in your connector code where input fields or output fields (datapills) are defined. This could be in places such as connection, actions, triggers and object_definitions

Quick Overview

Getting the hang of Workato schema is essential for building user-friendly and intuitive connectors. You can give various attributes to your connector to make it easier to use. This schema is a simple array of hashes where each index in the array represents a single input field or output field.

These definitions are completely interchangeable for both input and output fields, making it easier for you to write the schema once and reuse it.

# Structure

  [
    {
      name: String,
      label: String,
      optional: Boolean,
      type: String,
      hint: String,
      of: String,
      properties: Array,
      control_type: String,
      toggle_hint: String,
      toggle_field: Hash,
      default: String,
      pick_list: String,
      delimiter: String,
      sticky: Boolean,
      convert_input: String,
      convert_output: String,
      change_on_blur: Boolean,
      support_pills: Boolean,
      custom: Boolean,
      extends_schema: Boolean,
      list_mode: String,
      list_mode_toggle: Boolean,
      item_label: String,
      add_field_label: String,
      empty_schema_message: String,
      sample_data_type: String,
      ngIf: String
    },
    {
      # Another field definition
    }
  ]

# Attribute description

Key Definition
name Required. The name of this field. For example id or created_at
optional Optional. Default is false. Applies to input fields and ensures user provides input for this field before running the recipe.
label Optional. All fields will have default labels based on the field name. Use this to change the default value of the field label.
hint Optional. This allows you to add some hints below the input field to guide the user. Links to documentation can be given using HTML syntax.
type Optional. The data type of this field. Default value is "string". Possible values are:
- "string"
- "integer"
- "number"
- "date_time"
- "date"
- "timestamp"
- "boolean"
- "object" - Must be accompanied with properties
- "array" - Must be accompanied with of
of Optional except when type: "array". Used in conjunction with Arrays to define the data type of the Array. Possible values are:
- "string"
- "integer"
- "number"
- "date_time"
- "date"
- "timestamp"
- "boolean"
- "object" - Denotes an array of objects. Must be accompanied with properties attribute.
properties Optional except when type: "object" or when type: "array" and of: "object". Accepts an array of schema to denote the properties of the object.
control_type Optional. This field relates only to input fields and it dictates the input field type to expose in a recipe. Default is "string". When this schema is used as an output field, this attribute is ignored.
Refer to the list of supported control types.
toggle_hint Optional. This represents the label of the primary toggle. See toggle fields for more information.
toggle_field Optional. Hash representing the secondary toggle for this input field. See toggle fields for more information.
default Optional. Allows you to set a default value for that input field.
pick_list Optional. If control_type is :select or :multiselect, this property is required. Allows you to reference a picklist defined in the pick_lists key or define one directly. If defining a picklist directly, provide the same 2D array describe here
options Synonymous with pick_list and used only for connection input fields.
delimiter: Optional unless control_type: "multiselect". This delimiter is used between each input the user provides.
sticky Optional. Use this property to make this field always visible as an input field. By default, inputs that are optional are hidden inside the optional fields dropdown. Use sticky: true so they show up beside required fields.
convert_input Optional. When defining input fields, values passed into these fields are assumed to be strings regardless of their type defined. convert_input allows you to convert and transform these inputs even before they are passed to your execute block's input argument. Learn more
convert_output Optional. When defining output fields, the name of each field is matched against the keys in the actual output of the execute lambda function. This does not, however, ensure that the value of the output matches the type declared for its matched field. convert_output allows you to convert and transform these inputs as well as correctly "cast" incoming values assigned to a specific output field. Learn more
change_on_blur Optional. When true, config fields and dependent fields only evaluate the value when the user blurs out of the field instead of after every keystroke. This parameter often doesn't need to be configured.
support_pills Optional. The default value is true. When false, this field doesn't allow datapills to be mapped to it. This parameter often doesn't need to be configured.
custom Optional. When true, a special marker is introduced to indicate to the user that this field is custom. Normally used when dynamically generating object definitions which may contain custom fields.
extends_schema Optional. Allows a field to behave like a config_field
list_mode Optional. Used when type: "array" and of: "object". Workato defaults to dynamic lists but this parameter allows you to set configure this input field to a static array input field. Possible values are:
- "static" - Users will define each index in this array.
- "dynamic" - Users can dynamically define each index in this array using list datapills.
list_mode_toggle Optional. Used when type: "array" and of: "object". Allows users to toggle between static and dynamic lists when working with arrays. Defaults to true. Set list_mode_toggle: false to disallow users to toggle list modes.
item_label Optional. Only used with control_type: "schema_designer" or control_type: "key_value". This allows you to configure the item name stated in the modal popup. Setting item_label: "Item Label" will result in the following:
add_field_label Optional. Only used with control_type: "schema_designer" or control_type: "key_value". This allows you to configure the label of the add button. Setting add_field_label: "Custom Add Label" will result in the following:
empty_schema_message Optional. Only used with control_type: "schema_designer" or control_type: "key_value". This allows you to configure the message when the input field is empty. Setting empty_schema_message: 'Custom empty schema message that allows to add field and generate schema' will result in the following:
sample_data_type Optional. Only used with control_type: "schema_designer". This allows you to configure the type of data the schema_designer input field will accept. Setting sample_data_type: 'csv' will result in the following: Other possible inputs are json_input and xml. The schema_designer defaults to json_input.
ngIf Optional. Allows you to define a boolean statement. If true, this field will be shown. The boolean statement can reference other inputs in the same schema. For example, ngIf: 'input.object_name != "approval"' where the root node is input and you can traverse to a specific field via dot notation. Click here for more details.
tree_options Optional. Only used when control_type: 'tree'. This allows you to control the behvaiour of the tree picklist. This key expects a Hash which has three possible keys - selectable_folder,multi_select and force_selection_hierarchy. Find out more here.

# Control types

The control_type key affects how users configure the input fields you define. For each input field (index in the schema array), you can control the look of it by assigning one of the values to the control_type attribute:

Control type Description
text Simple text input field with formula mode option.
text control type
text-area Long text input field with formula mode option.
text-area control type
plain-text Simple text input field without formula mode option.
plain-text control type
plain-text-area Long text input field with formula mode option. This input field can be expanded using the adjust icon.
plain-text-area control type
password Text input field specifically designed for sensitive information (e.g. password). Text input in this control type will be masked.
password control type
number Simple number field with icon to indicate either an integer or float value. This control type has formula mode option.
number control type
url Text field with icon to indicate a URL value. This control type has formula mode option.
url control type
select Control type to provide a predefined list of values to choose from. Make sure to include the pick_list property.
select control type
checkbox Simple Yes/No select interface. It is advisable to add a toggle field for dynamic mapping and formula mode option.
checkbox control type
multiselect Control type similar to select with additional ability to select multiple values. This control type must be accompanied with pick_list and delimiter properties.
multiselect control type
date Control type indicating date value. This control type has formula mode option. date control type
date_time Control type indicating date with time value. This control type has formula mode option. date_time control type
phone Control type indicating phone value. This control type has formula mode option.
phone control type
email Control type indicating email value. This control type has formula mode option.
email control type
subdomain Control type to indicate a subdomain of a particular site. Typically used in connection fields. Make sure to include the url property.
subdomain control type
schema_designer Control type that allows you to collect schema information from users. This is useful when you need users to give your input during recipe design time to create input or output fields. This requires extends_schema: true to take effect.
{
  name: "schema",
  extends_schema: true,
  schema_neutral: false,
  control_type: 'schema-designer',
  label: 'Schema designer label',
  hint: 'Hint for schema designer field',
  item_label: 'button',
  add_field_label: 'Custom Add Label',
  empty_schema_message: 'Custom empty schema message that allows to <button type="button" data-action="addField">add field</button> and <button type="button" data-action="generateSchema">generate schema</button>',
  sample_data_type: 'csv' # json_input / xml
},
schema_designer
key_value Control type that allows you to collect key and value pairs from users. This is useful when you need users to give your input during recipe design time for URL query parameters. Must be accompanied with `properties` defined and two inputs given.
{
  name: "key_value",
  control_type: "key_value",
  label: "key_value",
  empty_list_title: "Add query parameters",
  empty_list_text: "Description for empty list",
  item_label: "Query parameter",
  type: "array",
  of: "object",
  properties: [
    { name: "key"},
    { name: "value"}
  ]
},
        
key_value

# Nested objects

Often, data returned from API request is not a simple one-level JSON. More often than not, the returned JSON object is much more complex, with multiple levels of nesting. This section aims to illustrate how to define nested fields.

# Sample code snippet

{
  "id": "00ub0oNGTSWTBKOLGLNR",
  "status": "STAGED",
  "created": "2013-07-02T21:36:25.344Z",
  "activated": null,
  "lastLogin": "2013-07-02T21:36:25.344Z",
  "profile": {
    "firstName": "Isaac",
    "lastName": "Brock",
    "email": "isaac.brock@example.com",
    "login": "isaac.brock@example.com",
    "mobilePhone": "555-415-1337"
  },
  "credentials": {
    "provider": {
      "type": "OKTA",
      "name": "OKTA"
    }
  },
  "_links": {
    "activate": {
      "href": "https://your-domain.okta.com/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/activate"
    }
  }
}

Nested object field profile can be defined type: :object with fields nested inside using properties. Properties should be an array of fields objects (just like fields within the user object).

object_definitions: {
  user: {
    fields: lambda do
      [
        {
          name: "id"
        },
        {
          name: "status"
        },
        {
          name: "created",
          type: :timestamp
        },
        {
          name: "activated",
          type: :timestamp
        },
        {
          name: "lastLogin",
          type: :timestamp
        },
        {
          name: "profile",
          type: :object,
          properties: [
            {
              name: "firstName"
            },
            {
              name: "lastName"
            },
            {
              name: "email",
              control_type: :email
            },
            {
              name: "login",
              control_type: :email
            },
            {
              name: "mobilePhone",
              control_type: :phone
            }
          ]
        }
      ]
    end
  }
}

# Nested Arrays

The other common type of nested field is array of objects. This type of field contains a list of repeated objects of the same fields. The defining such fields will be very similar to defining objects. Take the following sample user object from Asana for instance.

# Sample code snippet

{
  "data": {
    "id": 12149914544379,
    "email": "eeshan@workato.com",
    "name": "Ee Shan",
    "workspaces": [
      {
        "id": 1041269201604,
        "name": "Workato"
      },
      {
        "id": 498346130780,
        "name": "Product Documentation"
      }
    ]
  }
}

The workspaces array should be given type: :array as well as of: :object. This tells the object_definitions framework that the field contains an array of objects. Similar to nested objects, you will need to define properties, which is an array of fields corresponding to the fields of each object in the workspaces array.

object_definitions: {
  user: {
    fields: lambda do
      [
        {
          name: 'id',
          type: :integer
        },
        { name: 'name' },
        {
          name: 'email',
          control_type: :phone
        },
        {
          name: 'workspaces',
          type: :array,
          of: :object,
          properties: [
            {
              name: 'id',
              label: 'Workspace ID',
              type: :integer
            },
            { name: 'name' }
          ]
        }
      ]
    end
  }
}

# Using toggle fields

Toggle fields are a special type of input fields that allow 2 input types. They are a great way you can introduce greater flexibility and increase usability in your input fields. When used, toggle fields allow users to switch between two control types.

TIP

Toggle fields are often used in conjunction with pick lists. Since pick lists produce dropdowns, users are unable to map datapills which they normally would in recipes. Toggle fields fix that by allowing them to toggle to plain text fields which can accept datapills.

# Sample code snippet

    input_fields: lambda do |object_definition, connection, config_fields|
      {
        name: "parser_id",
        label: "Document Parser",
        hint: "The Document Parser the file gets imported to",
        control_type: :select,
        pick_list: "parsers",
        optional: false,
        toggle_hint: "Select from list",
        toggle_field: {
          name: "parser_id",
          label: "Parser ID",
          type: :string,
          control_type: "text",
          optional: false,
          toggle_hint: "Use Parser ID",
          hint: "Go to home page and select the required parser. If the URL is 'https://app.docparser.com/stack/ynrqkdxvaghs/overview', then 'ynrqkdxvaghs' is the ID"
        }
      },
    end

toggle primary Primary toggle field

toggle secondary Secondary toggle field

A pick list input type is used to create a more desired user experience. However, this makes the action value mapping static. Because the pick list can only select a single value, all recipe jobs executing this action will use the single parser ID value selected. To overcome this limitation, a text field should be used. A text field allows dynamic mapping of input field value.

In this case, since both field types are desired, a toggle_field can be used to provide both input options to users. Since, it is a more common scenario for users to select a parser per action, pick list type is set as the primary toggle and text field set as the secondary (nested toggle_field).

# Using fields with extends_schema

Sometimes, what input fields to show in an action depend on the answer to an input field in the same action. Rather than using config fields, extends_schema is an advanced way of introducing even more dynamic behaviour into your action.

# Sample code snippet

object_definitions:
  schema_input: {
    fields: lambda do |connection, config_fields, object_definitions|
      input_schema = parse_json(config_fields.dig('schema') || '[]')
      [
        {
          name: "schema",
          extends_schema: true,
          schema_neutral: false,
          control_type: 'schema-designer',
          label: 'Schema designer label',
          hint: 'Hint for schema designer field',
          item_label: 'button',
          add_field_label: 'Custom Add Label',
          empty_schema_message: 'Custom empty schema message that allows to <button type="button" data-action="addField">add field</button> and <button type="button" data-action="generateSchema">generate schema</button>',
          sample_data_type: 'csv' # json_input / xml
        },
        if input_schema.present?
          { name: 'data', type: 'object', properties: input_schema }
        end
      ].compact
    end
  }

In the code sample above, we use an input field with the control_type schema_designer. When extends_schema is set to true, any inputs by an end user immediately cause the object_definitions block to re-evaluate itself with the inputs given being passed as config_fields again. In this example, we see that the input schema is referenced at the beginning of the object_definition block. When users give inputs for the schema field, it can be referenced as a config_field which use to build the data input field.

# Arrays of primitive scalar data types

Arrays in Workato input and output schema currently only work with objects. In cases where you need to collect an array of primitive datatypes such as strings or integers, consider the code below. In this example, we hope to send an array of strings to a target API in the format ["column1","column2","column3"]. This can be done by declaring an array of objects with the declaration for the column names input field wrapped inside the object layer.

# Sample code snippet

object_definitions: {
  columns: {
    fields: lambda do
      [
        {
          label: 'String Array',
          name: 'array_of_strings',
          type: "array",
          of: "string"
        }
      ]
    end
  }
}

# Using ngIf to conditionally hide or display fields

Sometimes, you need to hide or display fields based on a user's input. This could be in the input_fields key or the config_fields key. For example, if you want to showcase an additional config_field based on a user's input to another config_field. Another use case would be to showcase a new input field based on a user's input for another input field. For example, if we have an action that creates a user - if one input field is assign_new_password, we would use ngIf to conditionally show new_password if the user gave true to that input.

# Sample code snippet

object_definitions: {
  create_user: {
    fields: lambda do |_connection, config_fields|
      [
        {
          name: 'assign_new_password',
          label: 'Assign new password during creation',
          hint: "Select <b>yes</b> to provide a password for this newly created user. If <b>no</b>, email will be sent to user to define their own password",
          control_type: 'checkbox',
          type: 'boolean',
          sticky: true,
        },
        {
          name: 'password_input',
          label: 'Custom password',
          control_type: 'password',
          ngIf: 'input.assign_new_password == "false"',
          sticky: true,
          hint: 'Required if <b>Assign new password during creation</b> is set to <b>yes</b>. ' \
                  'Provide a password of length 8 to 100 characters.'
        },
      ]
    end
}

# Sample code snippet

object_definitions: {
  create_user: {
    fields: lambda do |_connection, config_fields|
      [
        {
          name: 'assign_new_password',
          label: 'Assign new password during creation',
          hint: "Select <b>yes</b> to provide a password for this newly created user. If <b>no</b>, email will be sent to user to define their own password",
          control_type: 'checkbox',
          type: 'boolean',
          sticky: true,
        },
        {
          name: 'password_input',
          label: 'Custom password',
          control_type: 'password',
          ngIf: 'input.assign_new_password == "false"',
          sticky: true,
          hint: 'Required if <b>Assign new password during creation</b> is set to <b>yes</b>. ' \
                  'Provide a password of length 8 to 100 characters.'
        },
      ]
    end
  }
}

# Using convert_input and convert_output for easy transformations

In most cases, when users map fields to your action's input fields, these values are assumed to be of the data type string regardless of the actual type or control_type you have configured for the input field.

For example, the schema below:

[
  {
    name: "account_id",
    control_type: "integer"
    type: "int"
  }
]

When the input is passed to the execute lambda, it would still arrive in the JSON object as

{
  "account_id": "123"
}

In these cases, you can use convert_input to transform the input into its expected data type even before it's passed to the execute lambda.

For example:

[
  {
    name: "account_id",
    control_type: "integer"
    type: "int",
    convert_input: "integer_conversion"
  }
]

With convert_input defined, the input argument passed to your execute lambda will be passed as:

{
  "account_id": 123
}

This allows you to configure the expected data formats when defining schema and allows you to skip any unnecessary code in your execute for ensuring data is in the correct format. The same behaviour is also seen in reverse when you use convert_output, where a value from the output of your execute lambda is transformed when its mapped to a output field which contains a convert_output attribute.

# Predefined convert_input values

  • "integer_conversion" - converts input into data type integer
  • "float_conversion" - converts input into data type float
  • "date_conversion" - converts input into data type date
  • "render_iso8601_timestamp" - converts input into date string that conforms to ISO8601 standards
  • "boolean_conversion" - converts input into data type boolean

# Predefined convert_output values

  • "integer_conversion" - converts output into data type integer/number
  • "float_conversion" - converts output into data type float
  • "date_conversion" - converts output into data type date
  • "date_time_conversion" - converts output into a format that matches Javascript's Date object's toJSON method
  • "boolean_conversion" - converts output into data type boolean

TIP

Sometimes the above transformations may not suite your needs. For example, when you need transformations to a specific time format or if you want to manipulate the structure of your data. In these cases, you can create your own custom convert_input and convert_output functions. Continue reading to learn how to do this.

# Advanced transformations using methods in convert_input and convert_output

In some cases, API's requires data to be sent in a specific format. For example, when an API requires date times to be sent in epoch time. In many cases, we cannot assume that users in the recipe editor are comfortable with epoch time or that the upstream systems they are mapping data from are giving date times in epoch format. In cases like these, we would use methods in conjunction with convert_input to cast their inputs to the proper format.

For example:

[
  {
    name: 'invoice_date',
    control_type: "date_time",
    convert_input: "epoch_time_conversion"
  }
]

and a matching method named epoch_time_conversion:

methods: {
  epoch_time_conversion: lambda do |val|
    val.to_time.to_i
  end
}

which would render inputs such as "2021-10-27T00:00:00-07:00" to 1635318000.

You may also use custom lambdas to do custom transformations on nested structures. For example, when an API requires that you to send a payload in the format:

{
  "data": {
    "name": {
      "value": "abc123"
    },
    "address": {
      "value": "def456"
    }
  }
}

The corresponding input_field representation might be overly nested, making it cumbersome for users of your connector.

Nested fields Input fields represented by the earlier example

Custom lambdas help you improve the UX of your connector by allow you to present a relatively flat input field structure and doing the transformations afterwards.

For example

[
  {
    name: 'data',
    type: 'object',
    properties: [
      { 
        name: "name" 
      },
      { 
        name: "address"
      }
    ],
    convert_input: "key_value_conversion"
  }
]

and a matching method named key_value_conversion:

methods: {
  key_value_conversion: lambda do |val|
    # val in this case is the entire "data" object
    val.map do |key, value|
      {
        key => {
          "value" => value
        }
      }
    end.inject(:merge)
  end
}

Resultant JSON:

{
  "data": {
    "name": {
      "value": "abc123"
    },
    "address": {
      "value": "def456"
    }
  }
}

Flattened fields Input fields that produce the same output due to custom lambdas

Furthermore, you can call convert_input and convert_output on schema of type arrays. This allows you to perform transformations on the entire array of inputs.

For example

[
  {
    name: 'products',
    type: 'array',
    of: 'object',
    convert_input: "product_conversion",
    properties: [
      {
        name: "name"
      },
      {
        name: "qty"
      }
    ]
  }
]

and a matching method named string_concat_conversion:

methods: {
  product_conversion: lambda do |val|
    # Render_input is called on each index of the array THEN the whole array
    if val.is_a?(Array)
      val
    else
      {
        val['name'] => val['qty']
      }
    end
  end,
}

The user's input would look something like this:

{
  "products": [
    {
      "name": "car",
      "qty": "100"  
    },
    {
      "name": "wrench",
      "qty": "10"  
    }
  ]
}

Resultant JSON:

{
  "products": [
    "car": "100" 
    "wrench": "10"
  ]
}