Actions in the Gladly App Platform allow you to perform operations directly from Gladly in external systems.
Using Actions
Use Data Pulls for all Customer queries
Retrieving Customer related data from external systems should generally be done via Data Pulls. Data retrieved via Actions will not be available to Heros, nor in Rules and Routing.
Actions are currently only available in Gladly Sidekick. Here are some use cases for Actions:
Order Management: cancelling an order, start a return, etc
Subscription Management: cancel, resume, change shipping date
Gladly Tasks
Actions vs Data Pulls
Actions are designed to be interactive processes that execute operations in external systems based on user input. They differ from Data Pulls in these key principles:
They do not have to be associated with a customer.
Actions can execute operations in an external system.
Errors encountered during an Action are returned to the client for user handling.
Data from actions is not persistent in Gladly.
Folder Structure
Action configuration is specified in the app's actions folder. Note that most files are optional.
Use appcfg to initialize an action
Use appcfg add action to create an "actions/{action name}" folder containing placeholders for all relevant action configuration files.
app/
├── actions/
│ ├── actions_schema.graphql
│ ├── _run_/
│ │ └── data/
│ │ └── [scenario_name]/
│ │ └── inputs.json
│ │
│ └── [action_name]/
│ ├── config.json
│ ├── headers/
│ │ ├── [header_name_1].gtpl
│ │ └── ...
│ │ └── [header_name_n].gtpl
│ ├── request_url.gtpl
│ ├── request_body.gtpl
│ ├── response_transformation.gtpl
│ ├── _run_/
│ │ └── data/
│ │ └── [scenario_name]/
│ │ └── inputs.json
│ └── _test_/
│ └── data/
│ ├── success/
│ │ ├── inputs.json
│ │ ├── expected_request_body.json
│ │ ├── rawData.json
│ │ └── expected_response_transformation.json
│ └── [error_case]/
│ ├── inputs.json
│ ├── expected_request_body.json
│ ├── rawData.json
│ └── expected_response_transformation.json
Key Components:
actions_schema.graphql – Defines the GraphQL schema for your actions
config.json – Configures the action, including HTTP method and content type
headers/ – Directory containing templates for custom request headers specific to the action.
request_url.gtpl – Template for the URL to call
request_body.gtpl – Template for the request body
response_transformation.gtpl – Template for transforming the response
_run_/ – Contains scenarios (subdirectories with
inputs.json
) for executing actionsappcfg run
._test_/ – Contains test data and expected outputs for various scenarios
Inputs
When the Gladly App Platform executes an action, it uses Go templates (.gtpl files) to dynamically construct the necessary HTTP request components: the URL, headers, and body. Input parameters are available to Action templates, on top of the Apps Configurations data.
The API expects the names and types specified in the input parameters of the GraphQL definition for this Action. Input parameter types can be GraphQL scalars or user-defined object types. The general best practice is to use scalar types to keep the configuration simple.
The input parameters are exposed to templates via the inputs
attribute, where each input is a corresponding attribute of the inputs
object made available to templates. The source of the values for these inputs will be configured via the Sidekick thread UI.
For example, GraphQL Action definition for canceling an order:
type Mutation {
cancelOrder(orderId: String!): CancelOrderResult! @action(name: "cancel_order")
}
The corresponding input template data for this action would be the following:
{
"inputs": {
"orderId": ""
}
}
And a request URL template that uses the parameter would look like:
https://hostname.com/cancel_order?customer_order_id={{.inputs.orderId}}
The complete data for action templates is as follows
{
"integration": {
"configuration": {
},
"secrets": {
}
},
"inputs": {
}
}
Configuration Files
config.json
[app folder]/actions/[action_name]/config.json
The config.json
file contains all of the configuration information that is not template-related. Each template is instead stored in its own file for readability and testing:
{
"httpMethod": "POST",
"contentType": "some content type",
}
Attribute | Type | Description |
---|---|---|
httpMethod | String | The HTTP method used to submit the request. E.g. GET, POST, PUT. |
contentType | String | The Content-Type if the request has a body. |
Template Files
Templates use the Go templating language with additional functions from the Sprig library.
request_url.gtpl
[app folder]/actions/[action_name]/request_url.gtpl
This template specifies the fully qualified request URL for the API call to the external system. The HTTP method used to submit the request is specified in the config.json
file. The template can access the data outlined in the Template Data available to URL, Header and Body templates section above.
For example, request URL template:
https://hostname.com/cancel_order?customer_order_id={{.inputs.orderId}}
request_body.gtpl
[app folder]/actions/[action_name]/request_body.gtpl
This template specifies the body's content for HTTP methods that require a body. The content type associated with the body is specified in the config.json
file. This template defines the request body:
{{/*
This template builds a GraphQL mutation to ship a subscription immediately.
It uses the subscriptionId input parameter to identify which subscription to ship.
*/}}
{{$mutation := `
mutation shipSubscriptionNow($input: ShipSubscriptionNowInput!) {
shipSubscriptionNow(input: $input) {
userErrors {
message
field
}
subscription {
id
}
}
}
`}}
{
"query": {{toJson $mutation}},
"variables": {
"input": {
"subscriptionId": "{{.inputs.subscriptionId}}"
}
}
}
Request headers
Configuration for any required request headers specific to the Action is located in the Actions’ headers
folder.
Each header is configured via a template that provides the template value. The template file for each header is named after the header.
response_transformation.gtpl
In many cases, the data returned as part of the response to an Action can contain some information irrelevant to Sidekick and/or not in an optimal format. The response transformation template is used to extract relevant data from the response and convert it into a more user-friendly format. The output of the template MUST be in JSON or XML format.
[app folder]/actions/[action_name]/response_transformation.gtpl
The response data transformation template has access to all of the data provided to the URL, body, and header templates, as well as the associated HTTP request and response data. Currently, response data formatted as JSON (application/json) or XML is supported. The response data is accessed using the rawData
attribute of the template data. In most cases, the response template will just use the data associated with the rawData
attribute.
{
"request": {
"url": "",
"method": "",
"headers": {
"request_header_name": []
},
"body": ""
},
"response": {
"status": "",
"statusCode": 200,
"headers": {
"response_header_name": []
},
"body": ""
},
"rawData": null
}
Attribute | Type | Description |
---|---|---|
request.url | String | The fully qualified URL of the request. |
request.method | String | HTTP method for the request. |
request.headers | Object | Each attribute corresponds to a request header. The value associated with each header is an array of String values representing one or more header values. |
request.body | String | The raw request body text. |
response.status | String | The HTTP status of the response. |
response.statusCode | Int | The HTTP status code of the response. |
response.headers | Object | Each attribute corresponds to a response header. The value associated with each header is an array of String values representing one or more header values. |
response.body | String | The raw response text. |
rawData | Object | The object representation of the JSON or XML response data. |
See the accompanying golang template documentation for the syntax of accessing template data.
The entire response template data looks as follows:
{
"integration": {
"configuration": {
},
"secrets": {
}
},
"inputs": {
},
"request": {
"url": "",
"method": "",
"headers": {
"request_header_name": []
},
"body": ""
},
"response": {
"status": "",
"statusCode": 200,
"headers": {
"response_header_name": []
},
"body": ""
},
"rawData": null
}
For example, hypothetical response data:
{
"orderId": "12345",
"status": "cancelled",
"refundIssued": true,
"refundAmount": "3.99",
"refundCurrency": "USD"
}
Response transformation template (output MUST be in JSON or XML format):
{{- if .rawData.refundIssued }}
{
"amount": "{{.rawData.refundAmount}}",
"currency": "{{.rawData.refundCurrency}}"
}
{{- else -}}
{{- /* return null when a refund has not been issued */ -}}
null
{{- end -}}
The resulting data format will be defined in the GraphQL schema and associated with the corresponding Action:
type Refund {
amount: Currency
currency: String
}
The field names and types defined in the GraphQL schema MUST match those in the JSON generated by the template.
GraphQL Schema
The inputs and outputs associated with each configured Action are defined using a single GraphQL schema.
[app folder]/actions/actions_schema.graphql
Actions that modify (add, update, delete) data in the external system should be defined under the GraphQL Mutation
type and Actions that retrieve data under the Query
type. Each Action defined in the GraphQL schema is associated with its corresponding Action configuration via the @action
directive. The name
attribute specified in the @action
directive corresponds to the folder name (Action name) in which the configuration for the corresponding Action resides.
@action
directive definition
# The name argument is the action name.
directive @action(name: String!) on FIELD_DEFINITION
For example, using the above sample cancel_order
Action:
# The format of the Refund return type corresponds to the output
# generated by the response_transformation.gtpl template.
type Refund {
amount: Currency
currency: String
}
type Mutation {
# Input parameters defined here are exposed via the
# "inputs" attribute of the data passed to the template.
# Each "inputs" child attribute is named after the
# corresponding GraphQL input parameter. I.e. a "inputs"
# attribute referenced in any of the cancel_order templates
# will match one of the input parameters defined here.
#
# cancelOrder is associated with the cancel_order action
# configuration via an @action directive. The action
# configuration is located in a subfolder named after the
# cancel_order @action directive "name" value.
"""
Cancel an order using the order ID
"""
cancelOrder(orderId: String!): Refund @action(name: "cancel_order")
}
Gladly supports the standard GraphQL data types (Int, Float, String, Boolean, ID) and the following additional types.
Data Type | Format | Description |
---|---|---|
DateTime | String value in ISO8601 date format | DateTime fields can be used in conditionals and can be manipulated. |
Currency | String representation of currency value (real number) | Representing currency values as a string ensures no loss of precision. Gladly ensures that no precision is lost when manipulating Currency values. |
Testing Your Actions
Testing is crucial to ensure your actions work correctly. The App Platform provides tools for testing:
Test Files
The _test_
directory contains test data for different scenarios:
inputs.json – The input parameters for the action
expected_request_body.json – The expected request body after template processing
rawData.json – The simulated response from the external system
expected_response_transformation.json – The expected output after transformation
It is best practice for each action to have make commands that run the action as well as its GraphQL query against the external system, for example, for our shipNow
action would look something like:
# Run shipNow mutation
ship-now-graphql:
appcfg run action-graphql -q shipNow -s '{"apiToken": "$(API_TOKEN)"}' -i '{"subscriptionId": "1e671650-4876-4egg-a0b7-130f355281ac"}' -r $(MAKEFILE_DIR)
# Run shipNow action using data in `success` folder
ship-now:
appcfg run action shipNow -s '{"apiToken": "$(API_TOKEN)"}' -d success -r $(MAKEFILE_DIR)
Test Data Structure
Your test data should closely mirror the actual data you expect to receive. Ideally, you should obtain your test data by calling the APIs directly.
// inputs.json
{
"subscriptionId": "sub_123456789"
}
// expected_request_body.json
{
"query": "mutation shipSubscriptionNow($input: ShipSubscriptionNowInput!) {\\\\n shipSubscriptionNow(input: $input) {\\\\n userErrors {\\\\n message\\\\n field\\\\n }\\\\n subscription {\\\\n id\\\\n }\\\\n }\\\\n}\\\\n",
"variables": {
"input": {
"subscriptionId": "sub_123456789"
}
}
}
// rawData.json (success case)
{
"data": {
"shipSubscriptionNow": {
"userErrors": [],
"subscription": {
"id": "sub_123456789"
}
}
}
}
// expected_response_transformation.json (success case)
{
"message": "Subscription shipped successfully",
"ok": true
}
Error Handling
Actions have specific error-handling requirements:
Client Interaction – Since actions are user-initiated, errors are returned to the client.
Mirror External Systems's Error Response – To aid debugging return errors in the same structure as received from external systems. Simplify the amount of data returned.
Actionable Errors – Return clear, actionable error messages that help the user understand what happened.
Error Scenarios – Test common error scenarios (not found, permission denied, etc.).
Example error handling:
{{if .rawData.data.shipSubscriptionNow.userErrors}}
{{$errors := .rawData.data.shipSubscriptionNow.userErrors}}
{{if gt (len $errors) 0}}
{
"message": "Failed to ship subscription: {{(index $errors 0).message}}",
"ok": false
}
{{end}}
{{else}}
{{- toJson .rawData.data.shipSubscriptionNow }}
{{end}}
Best Practices
To make apps that are easy to maintain and provide a great experience, adhere to the following best practices:
Action vs Data Pulls
Before you decide to write an action, consider that actions are currently only available in Sidekick, and any data fetched with an action will not be available in other parts of Gladly (e.g., Rules or Customer Profile page).
Values returned by Queries and Mutations (e.g., the Refund from cancelOrder above) become available memories in Sidekick Thread Builder. Consider this when deciding what response data to expose via the response_transformation.gtpl
template.
Action Names and Inputs
Action names will be displayed as they are named in the GraphQL file. Action descriptions shown in the UI will be taken from the descriptions associated with each field in the GraphQL schema. Choose action and input parameter names that are self-explanatory and descriptive. Add comments based on the external systems documentation. The comments should be readable by a non-technical person. They should describe the outcome of the action in the external system.
Values returned by Queries and Mutations (e.g., the Refund from cancelOrder above) become available memories in Sidekick Thread Builder. Consider this when deciding what response data to expose via the response_transformation.gtpl template.
Use flat input structures to keep the configuration simple.
Template design
Check for required inputs before processing.
App Platform uses templates as they are easily readable to a human. When designing templates, consider their readability and maintenance.
Consider what happens if data is missing or in an unexpected format.
Keep field names and structure close to the original API for easier debugging.
Experience Design
User-Centric Design – Design actions based on the user experience you want to provide.
Clear Success/Failure Indicators – Make it obvious whether an action succeeded or failed.
Actionable Errors – Pass on any errors that are actionable by the end user to the client. Use
fail
for any unexpected errors.Consider the Workflow – Consider where the action fits the user workflow. For example, some tasks will require the implementation of multiple actions, e.g.
cancelOrder
andcheckCancelStatus
.
Iterative Development Process
Following an iterative process can help ensure your actions work correctly:
Plan & Setup – Define the use case, review API docs, identify inputs/outputs, and document the action.
Create Initial Files – Use
appcfg add action
to generate the basic structure.Define GraphQL Schema – Create output types and action definitions in
actions_schema.graphql
.Implement Basic Request – Build initial request URL and body templates.
Test & Refine – Run the action, analyze the output, and refine the implementation.
Add Test Cases – Create test cases for success and error scenarios.
Finalize & Document – Update the README, refine error handling, and complete documentation.