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.
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.
The following memories are accessible in every flow automatically.
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
}
}
}
}
}
}
}
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 will be true when interacting with a console ticket, enabling custom behavior while testing.
{
"type": "bool"
}
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
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.