--- title: "App Platform Apps Development" slug: "app-platform-apps-developement" description: "Integrate AI models like Claude or Gemini into your Gladly App Platform workflow, streamlining REST-via-GraphQL configurations for efficient data handling." updated: 2026-01-26T20:14:13Z published: 2026-01-26T20:14:13Z canonical: "help.gladly.com/app-platform-apps-developement" --- > ## Documentation Index > Fetch the complete documentation index at: https://help.gladly.com/llms.txt > Use this file to discover all available pages before exploring further. # App Platform Apps Development 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 - [Go text template language](https://pkg.go.dev/text/template) - [Sprig template functions](https://masterminds.github.io/sprig) - [Gladly template functions](https://github.com/gladly/app-platform-appcfg-cli/blob/main/docs/appcfg_platform_template-functions.md) - [Gladly XML DOM](https://github.com/gladly/app-platform-appcfg-cli/blob/main/docs/appcfg_platform_xml.md) ## Directory Structure ```plaintext {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: ```go {{ 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](https://pkg.go.dev/time#pkg-constants)) ## Authentication Header Context ```plaintext {    "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 ```plaintext {    "httpMethod": "GET|POST|PUT|PATCH|DELETE",    "contentType": "application/json",  // Omit for GET    "rawResponse": false                 // Set true for non-standard content types } ``` ### Template Context ```plaintext {    "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 ```plaintext {    "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 ```plaintext {    "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: ```go {{- range .customer.emailAddresses -}} https://api.example.com/search?email={{urlquery .}} {{ end -}} ``` **Multiple Bodies** - Use `#body` marker: ```go {{- 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`: ```graphql 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`: ```graphql 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 ```plaintext {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