Contact Log in

Static Typing

Thankful supports optional static typing for flows. By default, flows are dynamically typed, and memories can be defined and re-defined with changing types throughout.

To support more complex flows and provide more robust error detection prior to flows reaching production, Thankful added static typing support. In our experience, static typing catches 90-95% of the most commonly experienced errors before they reach production and are thus well worth the upfront time investment.

Note that static typing is optional. It is “off” by default. You can opt into it by setting the STATIC_TYPING variable to any value in an execute JS step. Be sure to include it as an “out memory.”

Thankful supports the following basic, static types:

number
bool
string

Thankful also supports objects. Every object must be declared in the CRM as an Object Type using the following example syntax for a theoretical user:

{
	"type": "object",
	"props": {
		"id": {"type": "number"},
		"createdAt": {"type": "string"},
		"tags": {
			"type": "string",
			"array": true,
			"nullItems": true
		},
		"shippingAddress": {
			"type": "object",
			"props": {
				"street1": {"type": "string"},
				"street2": {
					"type": "string",
					"null": true
				},
				"city": {"type": "string"},
				"state": {"type": "string"},
				"zip": {"type": "string"},
				"countryCode": {"type": "string"}
			}
		}
	}
}

The following object is an example which satisfies our user type:

{
	"id": 1,
	"createdAt": "2020-01-01T00:00:00Z",
	"tags": ["tag1", null, "tag3"],
	"shippingAddress": {
		"street1": "100 Penn Ave",
		"city": "Los Angeles",
		"state": "California",
		"zip": "90001",
		"countryCode": "US",
	}
}

By default, types and object fields cannot be null and must be provided to avoid errors. Since “street2” specifies {"null": true}, we can omit it in our object. Any field which is not declared as null must be present with a non-null value.

Extra fields beyond those defined in the object type may be included in data, but they will be discarded before continuing the flow. This is especially useful to discard unneeded data from a webhook response to an API endpoint automatically, rather than needing to define types for every field in a response object.

The outermost type itself cannot be null or an array, since that is handled within flows through an inline syntax, in the same style as Typescript:

myNum: number
numOrNull: number|null
numArrayOrNull: number[]|null
numArrayWithNullableItems: [number|null]
numArrayWithNullableItemsOrNull: [number|null]|null

In the case of our user example above, we could define a users array which cannot be null utilizing the type we defined in the CRM like so:

users: user[]

The name of the memory is always on the left side of the :, and the right side is always a type.

Union types such as number|string are not supported.

Generics

When creating or retrieving objects from the CRM, they’re always wrapped with several fields:

{
	"id": {"type": "number"},
	"byteSize": {"type": "number"},
	"version": {"type": "number"},
	"createdAt": {"type": "string"},
	"data": {...}
}

The data you’ve passed to Create Object would be present in the data field.

To avoid needing to declare two versions of every type–the type itself and a CRM-wrapped-version of that type–we provide a generic type, crmObject which can be used like so:

crmObject<user>

In this case, our user type will be contained within the data field. The type passed to crmObject<___> can be any type defined in the CRM.

Other generic types beyond crmObject are not supported.

Built-in memories

The following memories are accessible in every flow automatically.

ticketV2

ticketV2 is present on all flows when processing a ticket in a helpdesk. Each field and its type definition are listed below for reference.

{
	"type": "object",
	"null": true,
	"props": {
		"url": {"type": "string"},
		"externalID": {"type": "string"},
		"externalURL": {"type": "string"},
		"brand": {"type": "string"},
		"subject": {"type": "string"},
		"status": {"type": "string"},
		"actionEnabled": {"type": "bool"},
		"channelEnabled": {"type": "bool"},
		"shouldClassify": {"type": "bool"},
		"shouldProcess": {"type": "bool"},
		"inHours": {"type": "bool"},
		"expectMoreMsgs": {"type": "bool"},
		"created": {"type": "bool"},
		"containsAbuse": {"type": "bool"},
		"handoffReason": {"type": "string"},
		"duplicateID": {
			"type": "string",
			"null": true
		},
		"fields": {
			"type": "string",
			"array": true
		},
		"tags": {
			"type": "string",
			"array": true
		},
		"messages": {
			"type": "object",
			"array": true,
			"props": {
				"content": {"type": "string"},
				"externalID": {"type": "string"},
				"to": {"type": "string"},
				"channel": {"type": "string"},
				"internalNote": {"type": "bool"},
				"createdAt": {"type": "string"},
				"user": {
					"type": "object",
					"props": {
						"externalID": {"type": "string"},
						"type": {"type": "string"},
						"name": {
							"type": "string",
							"null": true
						},
						"emails": {
							"type": "string",
							"array": true
						},
						"phones": {
							"type": "string",
							"array": true
						}
					}
				},
				"attachments": {
					"type": "object",
					"array": true,
					"props": {
						"name": {
							"type": "string",
							"null": true
						},
						"url": {
							"type": "string",
							"null": true
						}
					}
				}
			}
		}
	}
}

greeting

Overriding greeting with a custom value, such as Howdy partner, will replace the default email greeting when it’s needed, depending on the channel.

{
	"type": "string",
	"null": true
}

console

Console will be true when interacting with a console ticket, enabling custom behavior while testing.

{
	"type": "bool"
}

Step-specific types

Webhook V2

The response memory saved by a Webhook V2 step will always be in the following form:

{
	"type": "object",
	"props": {
		"statusCode": {"type": "int"},
		"body": {"type": "string"},
		"headers": {
			"type": "object",
			"props": {
				$yourHeaderName: {
					"type": "string",
					"array": true
				}
			}
		}
	}
}

You will need to define this memory in the CRM to use it.

Replace $yourHeaderName with the header names you’re interested in, or remove the headers field if you only need the body.

You can parse the response body in an Execute JS step into a specific object type (also defined in the CRM).

Question, Extract Data

Question and Extract Data steps save memories whose types change depending on the Answer Type selected. Each of the types for the varying answer types are documented below:

{"type": "bool"}
{"type": "number"}
{"type": "string"}
{
	"type": "string",
	"array": true
}
{
	"type": "object",
	"array": true,
	"props": {
		"name": {"type": "string"},
		"confidence": {
			"type": "number",
			"null": true
		}
	}
}

You will need to define this type in the CRM to use it.

{
	"type": "object",
	"props": {
		"name": {"type": "string"},
		"color": {"type": "string"},
		"size": {"type": "string"},
		"company": {"type": "string"},
		"quantity": {"type": "number"}
	}
}

You will need to define this type in the CRM to use it.

{
	"type": "object",
	"props": {
		"street1": {"type": "string"},
		"street2": {
			"type": "string",
			"null": true
		},
		"city": {"type": "string"},
		"state": {"type": "string"},
		"zip": {"type": "string"},
		"country": {"type": "string"}
	}
}

You will need to define this type in the CRM to use it.

Ask Support