input.ts configuration


info

You can deploy a connector with just Raw HTTP operation and test it in the UI. This would confirm if you have set up the custom service properly and your auth works.

Check out the Raw HTTP quickstart to see how it's done.

You can now move on to building operations.

Introduction to the input.ts file

This file exports an object with fields that define the user inputs to perform an operation.

The following sections outline a number of different ways to render your input fields in Tray's builder UI by adding JSdoc annotations to the properties.

Data types


warning

You must define the data type for each field.

Following data types are supported:

  • string: represents string values
  • number: represents a number, e.g. 42. If you want to limit an input field to a whole number you can use the JSdoc annotation @TJS-type integer above the number.
  • boolean: is for the two values true and false
  • array: represented by [] , e.g. number[]
  • object: represented by object that contains different field

Here is an example use of the different data types:

Copy
Copied
export type CreateUserInput = {
  name: string; 
  age: number;
  /**
   * @TJS-type integer
   */
  id: number;
  isActive: boolean;
  address: {      //address could be defined as a separate object and referred here
    city: string,
    country: string
  };
  tags: string[]; //Array of strings
};
info

Notice the use of @TJS-type integer annotation to make it an integer only field on the Tray builder UI.

Required/Optional fields


By default all input fields are mandatory, you can set any to optional with a ? in your TypeScript type. Mandatory fields get a red * in the Tray builder UI and will warn the user that they must be filled in before attempting to run a workflow.

Copy
Copied
export type CreateUserInput = {
  name: string; 
  age: number;
  /**
   * @TJS-type integer
   */
  id: number;
  isActive: boolean;
  address?: {       //optional field indicated by a ?
    city: string,
    country: string
  };
  tags?: string[];  //optional field indicated by a ?
};

Enums (Static dropdown lists)


Enums are rendered in the Tray builder UI as dropdowns.

The user will see the enum display names and the enum values are what will be passed into the handler.

By default, user friendly enum display names are generated. e.g. an enum of value my-enum-value will be rendered in the UI with the display name My enum value.

Copy
Copied
export type CreateUserInput = {
  name: string; 
  age: number;
  /**
   * @TJS-type integer
   */
  id: number;
  isActive: boolean;
  address?: {       //optional field indicated by a ?
    city: string,
    country: string
  };
  tags?: string[];  //optional field indicated by a ?
};

enum userStatus {
	active = 'active',     // User will see an auto generated display value: Active
	inactive = 'inactive', // User will see an auto generated display value: Inactive
	deleted = 'deleted',   // User will see an auto generated display value: Deleted
}

You may want to provide custom enum display values instead.

To do so use the JSdoc annotation @enumLabels followed by a comma separated list of strings that matches the order you have defined your enums in.

Copy
Copied
export type CreateUserInput = {
  name: string; 
  age: number;
  /**
   * @TJS-type integer
   */
  id: number;
  isActive: boolean;
  address?: {       //optional field indicated by a ?
    city: string,
    country: string
  };
  tags?: string[];  //optional field indicated by a ?
};

/**
 * @enumLabels Activated, Disabled, Removed
 */
enum userStatus {
	active = 'active',      // User will see: Activated
	inactive = 'inactive',  // User will see: Disabled
	deleted = 'deleted',    // User will see: Removed
}

Default values


If you want to provide a default initial value for a field you can use the JSdoc annotation @default.

Copy
Copied
export type ExampleOperationInput = {
  /**
   * @default string default
   */
  name: string;
  /**
   * @default 0.1
   */
  age: number;
  /**
   * @TJS-type integer
   * @default 1
   */
  id: number;
  /**
   * @default true
   */
  isActive: boolean;
  /**
   * @default { "city": "London", "country": "UK" }
   */
  address: object;
  /**
   * @default ["new_user"]
   */
  tags: string[];
};

Advanced fields (Hidden by default)


If you have optional fields that are for more advanced use cases you can obscure these into the Tray builder UI's advanced fields section.

This can provide a cleaner interface for your operation, but can still be accessed by the user by expanding the advanced fields section.

You can add fields here by using the JSdoc annotation @advanced true.

Copy
Copied
export type ProjectConfigurationInput = {
  projectName: string;       // The name of the project, a basic required field
  /**
   * @advanced true
   */
  deploymentRegion?: string; // An advanced field for specifying the deployment region, hidden by default
};

DDL (Dynamic dropdown lists)


Sometimes you want to provide the user a dropdown list, but you don't know the items to display in the list because they come from another API.

For example you may want to display a list of user names in a dropdown and based on the selection send the user ID. DDL's solve this problem.

Once you have created an operation for your DDL (check Building a DDL operation page on how to do it) you can use this DDL in the input of other operations (check Using a DDL operation page on how to do it).

The JSdoc annotation @lookupOperation specifies what operation to invoke when the user expands the dropdown.

Here is an example input.ts for Slack's send_message operation that would use list_user_ddl to present a dropdown list of users to whom the message can be sent.

Copy
Copied
export type SendMessageInput = {
  /**
   * @title user
   * @lookupOperation list_users_ddl
   * @lookupInput {"includePrivateChannels": false,"workspaceId": "{{{workspaceId}}}"}
   * @lookupAuthRequired true
   */
  userId: string;
  message: string;
};

@lookupInput is used to describe the JSON payload to send to that DDL operation.

Any inputs defined in input.ts can be passed to the DDL operation using triple braces. In this example we send the workspaceId field value by doing {{{workspaceId}}}.

If your DDL operation calls an authenticated endpoint you can pass along the token used in the current operation by setting @lookupAuthRequired true.

Union types (Supporting multiple types)


If you want to support two or more different object types in your schema you can achieve this using TypeScript unions.

In the example below our input accepts an array of elements.

Each element of this array can be either of type image or text.

When the user adds an item to the array they will see a dropdown where they can select if this element will be an image or text.

The JSdoc annotation @title on the image and text types will be displayed in the dropdown the user sees.

Copy
Copied
export type PageContentInput = {
  elements: ContentBlock[];
};

type ContentBlock = ImageBlock | TextBlock;

/**
 * @title Image
 */
type ImageBlock = {
  name: string;
  src: string;
};

/**
 * @title Text
 */
type TextBlock = {
  text: string;
};

Formatting fields


By default properties on your input type render as simple input fields.

You can select a more user friendly way to render the field based on your needs by using the JSdoc annotation @format.

The format options available are:

  • datetime - renders a date and time picker
  • code - renders a button which on click opens a modal with a simple code editor
  • text - for longer text inputs, expands when you enter new lines
Copy
Copied
export type BlogPostInput = {
  /**
   * @format datetime
   */
  publishDate: string;
  /**
   * @format code
   */
  contentHtml: string; 
  /**
   * @format text
   */
  authorNotes: string;
};

Reusing types across multiple operations


You can reuse types by importing them and reuse sections of a schema by using TypeScript intersections.

input.tsNotificationSettings.tsPrivacySettings.ts
Copy
Copied
import { NotificationSettings } from "./NotificationSettings";
import { PrivacySettings } from "./PrivacySettings";

export type UserSettingsInput = AccountSettings & NotificationSettings & PrivacySettings;

//AccountSettings is specific to this file and hence defined here
type AccountSettings = {
  username: string;
  email: string;
  password: string;
  notificationSettings: NotificationSettings; //imported from another operation
  privacySettings: PrivacySettings;           //imported from another operation
};
Copy
Copied
export type NotificationSettings = {
  emailNotifications: boolean;
  pushNotifications: boolean;
  smsNotifications: boolean;
};
Copy
Copied
export type PrivacySettings = {
  showProfilePicture: boolean;
  shareStatusUpdates: boolean;
  allowFriendRequests: boolean;
};

Once the imports and intersection are resolved, the input type would look like this:

Copy
Copied
export type Input = {
  username: string;
  email: string;
  password: string;
  notificationSettings: {
    emailNotifications: boolean;
    pushNotifications: boolean;
    smsNotifications: boolean;
  };
  privacySettings: {
    showProfilePicture: boolean;
    shareStatusUpdates: boolean;
    allowFriendRequests: boolean;
  }
};