App Platform Apps Development

Prev Next

This skill is designed to integrate AI models like Claude or Gemini into your Gladly App Platform workflow. By offloading the technical specifics of REST-via-GraphQL configurations to the AI, you can streamline the process of creating, modifying, and testing integrations. Use this to let the model handle the underlying schema complexity, allowing you to focus on the broader application logic.

Overview

App Platform Apps retrieve data and take actions in external systems using REST APIs. Configuration uses Go text templates for generating REST requests and transforming responses. APIs are exposed via GraphQL Query and Mutation operations.

Key Documentation References

Directory Structure

{app-name}/
├── manifest.json                          # App metadata with semver version
├── authentication/
│   ├── headers/                           # Auth header templates (*.gtpl)
│   ├── request_signing/                   # Request signing header templates
│   └── oauth/
│       ├── config.json                    # OAuth grant type config
│       ├── auth_code/                     # Authorization code request
│       ├── access_token/                  # Access token request
│       └── refresh_token/                 # Token refresh request
├── actions/
│   ├── actions_schema.graphql             # GraphQL schema for actions
│   └── {action-name}/
│       ├── config.json                    # HTTP method, content type
│       ├── request_url.gtpl               # URL template
│       ├── request_body.gtpl              # Body template (if needed)
│       └── response_transformation.gtpl   # Response transformation
└── data/
    ├── data_schema.graphql                # GraphQL schema for data pulls
    └── pull/{data-pull-name}/
        ├── config.json                    # HTTP method, data type, dependencies
        ├── request_url.gtpl               # URL template
        ├── request_body.gtpl              # Body template (if needed)
        ├── response_transformation.gtpl   # Response transformation
        ├── external_id.gtpl               # Extract unique ID
        ├── external_updated_at.gtpl       # Extract last updated timestamp
        └── external_parent_id.gtpl        # Extract parent ID (if applicable)

Go Template Guidelines

Range Actions

Reference root context with $ inside range loops:

{{ range $i, $customer := .customers}}
Customer {{ $i }} of {{ len $.customers }}
Name: {{ $customer.firstName }} {{ $customer.lastName }}
{{ end }}

Sprig Function Notes

  • contains: First param is substring, second is string to search: {{ contains "hello" "hello world" }} returns true

  • Date functions use Go time layout format (see time package constants)

Authentication Header Context

{
    "integration": {
        "configuration": {},  // App config including credentials
        "secrets": {}         // Sensitive credentials
    }
}

OAuth Configuration

Grant Types

  • authorization_code - Requires authDomain in config.json

  • client_credentials

  • password

Each OAuth request type (auth_code, access_token, refresh_token) has:

  • config.json - HTTP method and content type

  • request_url.gtpl - URL template

  • request_body.gtpl - Body template (for POST)

  • headers/ - Optional request-specific headers

Actions

Actions perform operations in external systems (cancel order, create return, etc.).

config.json

{
    "httpMethod": "GET|POST|PUT|PATCH|DELETE",
    "contentType": "application/json",  // Omit for GET
    "rawResponse": false                 // Set true for non-standard content types
}

Template Context

{
    "integration": { "configuration": {}, "secrets": {} },
    "inputs": {}  // GraphQL field input parameters
}

GraphQL Schema (actions_schema.graphql)

  • Use @action(name: "action-name") directive to link fields to actions

  • Define types before referencing them (no forward declarations)

  • No union types supported

  • Use DateTime scalar for ISO8601 timestamps

  • Use Currency scalar for currency strings

  • Add descriptions for non-obvious fields using """ description """

Response Transformation

  • Output must conform to GraphQL return type

  • Handle non-success status codes (except 401/403 auth errors)

  • Use fail function for unexpected errors

  • Convert timestamps to ISO8601 format

Data Pulls

Data pulls retrieve data from external systems.

config.json

{
    "dataType": { "name": "order", "version": "1.0" },
    "dependsOnDataTypes": ["customer"],  // Execute after these pulls
    "httpMethod": "GET|POST",
    "contentType": "application/json",   // Omit for GET
    "rawResponse": false
}

Template Context

{
    "integration": { "configuration": {}, "secrets": {} },
    "customer": {
        "id": "gladly-customer-id",
        "name": "John Smith",
        "primaryEmailAddress": "john@email.com",
        "emailAddresses": ["john@email.com"],
        "primaryPhoneNumber": { "number": "+15551234567", "type": "MOBILE" },
        "phoneNumbers": [{ "number": "+15551234567", "type": "MOBILE" }]
    },
    "externalData": {
        "dependent_type_name": []  // Data from dependent pulls
    }
}

Multiple Requests

Multiple URLs - Output each URL on a new line:

{{- range .customer.emailAddresses -}}
https://api.example.com/search?email={{urlquery .}}
{{ end -}}

Multiple Bodies - Use #body marker:

{{- range .customer.emailAddresses -}}
#body
{ "emailAddress": "{{.}}" }
{{ end -}}

GraphQL Schema (data_schema.graphql)

  • Use @dataType(name: "type-name", version: "1.0") directive

  • Define types before referencing them

  • Use DateTime and Currency scalars (provided by runtime)

  • Add descriptions for non-obvious fields

Parent/Child Relationships

Parent references child IDs - Use @childIds:

type Customer @dataType(name: "customer", version: "1.0") {
    customerId: ID!
    orders: [Order] @childIds(template: "{{.orderIds | join ','}}")
}

Child references parent ID - Use @parentId and external_parent_id.gtpl:

type Customer @dataType(name: "customer", version: "1.0") {
    id: ID!
    orders: [Order] @parentId(template: "{{.id}}")
}

Metadata Templates

  • external_id.gtpl - Extract unique ID: {{- .orderId -}}

  • external_updated_at.gtpl - Extract ISO8601 timestamp

  • external_parent_id.gtpl - Extract parent ID for relationships

  • Use int64 for numeric IDs: {{- int64 .reviewId -}}

  • Trim whitespace in output

Response Transformation

  • Return empty array [] when no data

  • Convert timestamps to ISO8601

  • Don't check statusCode unless rawResponse: true (platform handles non-200)

Testing

Test Directory Structure

{template-directory}/
└── _test_/
    ├── integration.json           # Shared test data (inherited)
    ├── {test-dataset-name}/
    │   ├── rawData.json           # Input: response data
    │   ├── customer.json          # Input: customer data
    │   ├── inputs.json            # Input: action inputs
    │   ├── externalData.json      # Input: dependent data
    │   ├── expected_request_url.txt
    │   ├── expected_request_body.json
    │   └── expected_response_transformation.json
    └── {another-test-dataset}/
        └── ...

Test Input Files

Named after context fields: rawData.json, customer.json, integration.json, inputs.json, externalData.json

Expected Output Files

Template

Expected File

Format

request_url.gtpl

expected_request_url.txt

Text

request_body.gtpl

expected_request_body.*

json/txt/bin

response_transformation.gtpl

expected_response_transformation.json

JSON

{header}.gtpl

expected_{header}.txt

Text

external_id.gtpl

expected_external_id.txt

Text (one per line)

external_updated_at.gtpl

expected_external_updated_at.txt

Text

external_parent_id.gtpl

expected_external_parent_id.txt

Text

Test Features

  • Test data files in _test_/ are inherited by subdirectories

  • Expected .txt files can use regex by wrapping content in /regex/

  • For fail/stop function tests, expected output is JSON string of the message

  • Metadata templates test against arrays in externalData.json, output one value per line

Best Practices

  1. Version Semantically - Major for breaking changes, minor for features, patch for fixes

  2. Add Field Descriptions - Document non-obvious fields in GraphQL schemas

  3. Handle Errors - Transform error responses appropriately, use fail for unexpected errors

  4. Timestamps - Always convert to ISO8601 format, use Z for UTC

  5. Test Coverage - Create test datasets for all conditional code paths

  6. Template Safety - Check field presence before referencing optional customer data