| name | generate-appian-app |
| description | Generate an importable Appian application package (ZIP) from an `API_Contract` JSON. The package includes record types, web APIs, groups, an application wrapper, DDL, and a manifest — everything needed to stand up the data model in an Appian environment. Use this skill when the user says "generate the Appian app", "create the Appian package", "build the app ZIP", or "hook this up to Appian". Prerequisite: run the `extract-prototype-contract` skill first.
|
Generate Appian App
Inputs
| Name | Type | Required | Description |
|---|
contract_path | string | No | Path to the API_Contract JSON. Defaults to appian-output/api-contract.json. |
output_dir | string | No | Output directory. Defaults to appian-output/. |
Outputs
All artifacts are written to <output_dir>/<app_name>/:
| Artifact | Description |
|---|
<app_name>/META-INF/MANIFEST.MF | Appian version manifest |
<app_name>/application/<app-uuid>.xml | Application wrapper |
<app_name>/group/<admin-uuid>.xml | Admin group |
<app_name>/group/<viewer-uuid>.xml | Viewer group |
<app_name>/recordType/<uuid>.xml | One per record type |
<app_name>/webApi/<uuid>.xml | One per endpoint |
ddl.sql | CREATE TABLE statements |
Instructions
Step 1 — Read and validate the contract
- Read the
API_Contract JSON from contract_path.
- Validate it has:
app_name, app_uuid, base_url, auth_tier, endpoints[], record_types[].
- Extract the prefix from the first record type name (e.g.,
"TD Task" → prefix "TD").
Step 2 — Generate UUIDs
Generate fresh UUIDs for:
- Admin group
- Viewer group
- Each web API endpoint (one UUID per endpoint)
- Version UUIDs for each object
Record type UUIDs come from the contract (already generated by the extraction skill).
Step 3 — Generate group XMLs
Admin group (<PREFIX> Administrators):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<groupHaul xmlns:a="http://www.appian.com/ae/types/2009">
<versionUuid><ADMIN_GROUP_UUID></versionUuid>
<group>
<name><PREFIX> Administrators</name>
<securityMap>SECURITYMAP_TEAM</securityMap>
<uuid><ADMIN_GROUP_UUID></uuid>
<groupTypeUuid>SYSTEM_GROUP_TYPE_CUSTOM</groupTypeUuid>
<description>Assigns Administrator permissions to users for objects in the application</description>
<delegatedCreation>false</delegatedCreation>
<memberPolicy>MEMBERPOLICY_CLOSED</memberPolicy>
<viewingPolicy>VIEWINGPOLICY_LOW</viewingPolicy>
<attributes/>
</group>
<members><users/><groups/></members>
<admins>
<users/>
<groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</admins>
<ruleSet/>
<history><historyInfo versionUuid="<ADMIN_GROUP_UUID>"/></history>
</groupHaul>
Viewer group (<PREFIX> Users):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<groupHaul xmlns:a="http://www.appian.com/ae/types/2009">
<versionUuid><VIEWER_GROUP_UUID></versionUuid>
<group>
<name><PREFIX> Users</name>
<securityMap>SECURITYMAP_TEAM</securityMap>
<uuid><VIEWER_GROUP_UUID></uuid>
<groupTypeUuid>SYSTEM_GROUP_TYPE_CUSTOM</groupTypeUuid>
<description>Assigns Viewer permissions to users for objects in the application</description>
<delegatedCreation>false</delegatedCreation>
<memberPolicy>MEMBERPOLICY_CLOSED</memberPolicy>
<viewingPolicy>VIEWINGPOLICY_LOW</viewingPolicy>
<attributes/>
</group>
<members>
<users/>
<groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</members>
<admins>
<users/>
<groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</admins>
<ruleSet/>
<history><historyInfo versionUuid="<VIEWER_GROUP_UUID>"/></history>
</groupHaul>
Step 4 — Generate record type XMLs
For each record type in the contract, generate a recordTypeHaul XML. This is the most complex artifact.
Field type mapping:
| Contract type | Appian XML type | Source field type |
|---|
Integer | {http://www.appian.com/ae/types/2009}Integer | INTEGER |
Text | {http://www.appian.com/ae/types/2009}Text | VARCHAR(255) |
Boolean | {http://www.appian.com/ae/types/2009}Boolean | BOOLEAN |
Datetime | {http://www.appian.com/ae/types/2009}Datetime | TIMESTAMP |
User | {http://www.appian.com/ae/types/2009}User | VARCHAR |
For each field, generate a UUID and a <field> block inside <sourceConfiguration>:
<field>
<uuid><FIELD_UUID></uuid>
<type><APPIAN_XML_TYPE></type>
<sourceFieldName><COLUMN_NAME></sourceFieldName>
<sourceFieldType><SOURCE_FIELD_TYPE></sourceFieldType>
<fieldName><camelCaseFieldName></fieldName>
<isRecordId></isRecordId>
<isUnique></isUnique>
<isCustomField>false</isCustomField>
<customFieldExpr/>
<customFieldDefaultValueStr>null</customFieldDefaultValueStr>
<fieldCalculationType>NA</fieldCalculationType>
<fieldTemplateType>NA</fieldTemplateType>
<isIndexable>false</isIndexable>
<subType>NA</subType>
<displayNameSource>STATIC</displayNameSource>
<descriptionSource>STATIC</descriptionSource>
<compositePkPrecedence>-1</compositePkPrecedence>
<isHidden>false</isHidden>
</field>
sourceFieldName: convert camelCase to SCREAMING_SNAKE_CASE (e.g., dueDate → DUE_DATE, id → ID)
isRecordId and isUnique: true only for the id field
For User-type fields, also generate a recordRelationshipCfg:
<a:recordRelationshipCfg>
<uuid><RELATIONSHIP_UUID></uuid>
<relationshipName><fieldName>User</relationshipName>
<targetRecordTypeUuid>SYSTEM_RECORD_TYPE_USER</targetRecordTypeUuid>
<relationshipType>MANY_TO_ONE</relationshipType>
<relationshipData>{"sourceRecordTypeFieldUuid":"<FIELD_UUID>","targetRecordTypeFieldUuid":"SYSTEM_RECORD_TYPE_USER_FIELD_username","relationshipOperator":"1","joinTableSourceField":"","joinTableTargetField":""}</relationshipData>
<updateBehavior>NON_CASCADING</updateBehavior>
</a:recordRelationshipCfg>
Full record type XML template:
<?xml version="1.0" encoding="UTF-8"?>
<recordTypeHaul xmlns:a="http://www.appian.com/ae/types/2009" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<versionUuid><VERSION_UUID></versionUuid>
<recordType a:uuid="<RECORD_TYPE_UUID>" name="<RECORD_TYPE_NAME>">
<a:pluralName><PLURAL_NAME></a:pluralName>
<a:description/>
<a:urlStub><SHORT_STUB></a:urlStub>
<a:source xsi:type="a:RecordsReplica"/>
<a:listViewTemplateExpr></a:listViewTemplateExpr>
<a:isSystem>false</a:isSystem>
<a:dataSrcExpr/>
<a:facetsListExpr/>
<a:titleExpr>rv!record[#"urn:appian:record-field:v1:<RECORD_TYPE_UUID>/<ID_FIELD_UUID>"]</a:titleExpr>
<a:defaultFiltersExpr/>
<a:layoutType>GRID</a:layoutType>
<a:security>16383</a:security>
<a:hideLatestNews>false</a:hideLatestNews>
<a:hideNewsView>false</a:hideNewsView>
<a:hideRelatedActionsView>false</a:hideRelatedActionsView>
<a:isExportable>true</a:isExportable>
<a:listViewSrcExpr/>
<a:recordViewSrcExpr/>
<a:recordTypeSearchCfg>
<searchFieldsSrc>DEFAULT</searchFieldsSrc>
<placeholderSrc>DEFAULT</placeholderSrc>
<placeholder/>
</a:recordTypeSearchCfg>
<a:iconId/>
<a:listAutoRefreshInterval>0.0</a:listAutoRefreshInterval>
<a:sourceConfiguration>
<sourceUuid><TABLE_NAME>@jdbc/Appian</sourceUuid>
<sourceType>RDBMS_TABLE</sourceType>
<sourceSubType>NONE</sourceSubType>
<friendlyName><TABLE_NAME></friendlyName>
<sourceFilterExpr/>
<uuid><SOURCE_CONFIG_UUID></uuid>
<refreshSchedule>
<value>{"hour":3,"minute":"00","amPM":"AM","timeZone":"America/New_York","version":"v1"}</value>
<activated>false</activated>
</refreshSchedule>
<skipFailureEnabled>true</skipFailureEnabled>
<recordIdGeneratorUuid/>
</a:sourceConfiguration>
<a:enabledFeatures>63</a:enabledFeatures>
<a:isVisibleInRecordTypeList>false</a:isVisibleInRecordTypeList>
<a:recordActionLaunchType>DIALOG</a:recordActionLaunchType>
<a:showSearchBox>true</a:showSearchBox>
<a:isVisibleInDataFabric>false</a:isVisibleInDataFabric>
<a:usesRollingSyncLimit>false</a:usesRollingSyncLimit>
<a:usesRecoverySync>false</a:usesRecoverySync>
<a:isScheduledIndexingEnabled>false</a:isScheduledIndexingEnabled>
<a:smartSearchAcceptableFailureRate>0.0</a:smartSearchAcceptableFailureRate>
<a:skipFailedSmartServicesSync>false</a:skipFailedSmartServicesSync>
<a:creationSource>UNKNOWN</a:creationSource>
</recordType>
<roleMap>
<role name="record_type_administrator">
<users/>
<groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</role>
<role name="record_type_viewer">
<users/>
<groups><groupUuid><VIEWER_GROUP_UUID></groupUuid></groups>
</role>
</roleMap>
<history>
<historyInfo versionUuid="<VERSION_UUID>"/>
</history>
<migrationVersion>1</migrationVersion>
</recordTypeHaul>
List view template expression:
Generate a basic recordGridField expression that shows all non-User fields as columns:
#"SYSTEM_SYSRULES_recordGridField"(
columns: {
<!-- one gridColumn per non-User field -->
#"SYSTEM_SYSRULES_gridColumn"(
label: "<Field Display Name>",
sortField: #"urn:appian:record-field:v1:<RECORD_TYPE_UUID>/<FIELD_UUID>",
value: fv!row[#"urn:appian:record-field:v1:<RECORD_TYPE_UUID>/<FIELD_UUID>"]
),
...
},
pageSize: 50,
shadeAlternateRows: true
)
Convert camelCase field names to Title Case for labels (e.g., dueDate → Due Date).
Step 5 — Generate web API XMLs
For each endpoint in the contract, generate a webApiHaul XML.
GET endpoint expression (uses system rule references):
a!localVariables(
local!records: #"SYSTEM_SYSRULES_queryRecordType_v2"(
recordType: #"urn:appian:record-type:v1:<RECORD_TYPE_UUID>",
pagingInfo: #"SYSTEM_SYSRULES_pagingInfo"(
startIndex: 1,
batchSize: 500
)
).data,
#"SYSTEM_SYSRULES_httpResponse_v1"(
headers: {
#"SYSTEM_SYSRULES_httpHeader"(name: "Content-Type", value: "application/json")
},
body: #"SYSTEM_SYSRULES_toJson_v1"(value: local!records)
)
)
To find the correct record type UUID: match the endpoint's response_fields or request_fields to a record type in the contract by comparing field names.
POST write endpoint expression (uses a!writeRecords directly — no process models):
a!localVariables(
local!value: cast(
'recordType!{<RECORD_TYPE_UUID>}<RECORD_TYPE_NAME>',
a!fromJson(http!request.body)
),
a!writeRecords(
records: local!value,
onSuccess: a!httpResponse(
statusCode: 200,
headers: {
a!httpHeader(name: "Content-Type", value: "application/json")
},
body: a!toJson(fv!recordsUpdated)
),
onError: a!httpResponse(
statusCode: 500,
headers: {
a!httpHeader(name: "Content-Type", value: "application/json")
},
body: a!toJson(
a!map(
message: "Write request has failed",
error: fv!error
)
)
)
)
)
POST delete endpoint expression:
a!localVariables(
local!id: a!fromJson(http!request.body).id,
a!deleteRecords(
records: {
'recordType!{<RECORD_TYPE_UUID>}<RECORD_TYPE_NAME>'(id: local!id)
},
onSuccess: a!httpResponse(
statusCode: 200,
headers: {
a!httpHeader(name: "Content-Type", value: "application/json")
},
body: a!toJson(a!map(success: true))
),
onError: a!httpResponse(
statusCode: 500,
headers: {
a!httpHeader(name: "Content-Type", value: "application/json")
},
body: a!toJson(
a!map(
message: "Delete request has failed",
error: fv!error
)
)
)
)
)
Web API XML template:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<webApiHaul xmlns:a="http://www.appian.com/ae/types/2009">
<versionUuid><VERSION_UUID></versionUuid>
<webApi a:uuid="<API_UUID>" name="<ENDPOINT_NAME>">
<a:description></a:description>
<a:expression><EXPRESSION></a:expression>
<a:urlAlias><ALIAS></a:urlAlias>
<a:httpMethod><METHOD></a:httpMethod>
<a:system>false</a:system>
<a:requestBodyType>NONE</a:requestBodyType>
<a:loggingEnabled>false</a:loggingEnabled>
</webApi>
<roleMap>
<role name="web_api_administrator">
<users/>
<groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</role>
<role name="web_api_viewer">
<users/>
<groups><groupUuid><VIEWER_GROUP_UUID></groupUuid></groups>
</role>
</roleMap>
<typedValue>
<type>
<name>WebApiRequest?list</name>
<namespace>http://www.appian.com/ae/types/2009</namespace>
</type>
<value>
<el>
<a:path/>
<a:headers>
<a:name>Content-Type</a:name>
<a:value>application/json</a:value>
</a:headers>
<a:body>{}</a:body>
</el>
</value>
</typedValue>
<history>
<historyInfo versionUuid="<VERSION_UUID>"/>
</history>
</webApiHaul>
For GET endpoints, the <typedValue> section should have <a:body/> (empty) instead of <a:body>{}</a:body>, and no <a:headers> block.
Step 6 — Generate application XML
Generate the application wrapper that references all generated objects:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<applicationHaul xmlns:a="http://www.appian.com/ae/types/2009">
<versionUuid><APP_VERSION_UUID></versionUuid>
<application>
<name><APP_NAME></name>
<uuid><APP_UUID></uuid>
<description>Generated from prototype</description>
<parentUuid>SYSTEM_APPLICATIONS_ROOT</parentUuid>
<visibility>
<advertise>false</advertise>
<hierarchy>true</hierarchy>
<indexable>true</indexable>
<quota>false</quota>
<searchable>true</searchable>
<system>false</system>
<unlogged>false</unlogged>
</visibility>
<urlIdentifier><SHORT_ID></urlIdentifier>
<published>false</published>
<public>false</public>
<prefix><PREFIX></prefix>
<associatedObjects>
<globalIdMap>
<item><type>group</type><uuids>
<uuid><ADMIN_GROUP_UUID></uuid>
<uuid><VIEWER_GROUP_UUID></uuid>
</uuids></item>
<item><type>recordType</type><uuids>
</uuids></item>
<item><type>webApi</type><uuids>
</uuids></item>
</globalIdMap>
</associatedObjects>
<applicationNavigation/>
<applicationActions/>
<associatedApplications>
<globalIdMap>
</globalIdMap>
</associatedApplications>
</application>
<roleMap>
<contentRoleMap public="true">
<role inherit="false" allowForAll="false" name="readers">
<users/><groups><groupUuid><VIEWER_GROUP_UUID></groupUuid></groups>
</role>
<role inherit="false" allowForAll="false" name="authors">
<users/><groups/>
</role>
<role inherit="false" allowForAll="false" name="administrators">
<users/><groups><groupUuid><ADMIN_GROUP_UUID></groupUuid></groups>
</role>
<role inherit="false" allowForAll="false" name="denyReaders"><users/><groups/></role>
<role inherit="false" allowForAll="false" name="denyAuthors"><users/><groups/></role>
<role inherit="false" allowForAll="false" name="denyAdministrators"><users/><groups/></role>
</contentRoleMap>
</roleMap>
<defaultApplicationObjects>
<object>
<objectKey>defaultAdminsGroup</objectKey>
<objectType>group</objectType>
<objectUuid><ADMIN_GROUP_UUID></objectUuid>
</object>
<object>
<objectKey>defaultUsersGroup</objectKey>
<objectType>group</objectType>
<objectUuid><VIEWER_GROUP_UUID></objectUuid>
</object>
</defaultApplicationObjects>
<applicationDocumentation/>
<history>
<historyInfo versionUuid="<APP_UUID>"/>
<historyInfo versionUuid="<APP_VERSION_UUID>"/>
</history>
<postDeploymentProcessData/>
</applicationHaul>
The globalIdMap in associatedObjects must include ALL type entries (even empty ones) to match the Appian export format. Use the Case Management export as a reference for the complete list of types.
Step 7 — Generate MANIFEST.MF
Manifest-Version: 1.0
Appian-Version: 26.3.0.65
Created-On: <ISO_TIMESTAMP>
Step 8 — Generate DDL
For each record type, generate a CREATE TABLE statement:
CREATE TABLE <TABLE_NAME> (
<COLUMN_NAME> <SQL_TYPE> PRIMARY KEY AUTO_INCREMENT,
<COLUMN_NAME> <SQL_TYPE>,
...
);
SQL type mapping:
| Appian type | SQL type |
|---|
Integer | INTEGER |
Text | VARCHAR(255) |
Boolean | BOOLEAN |
Datetime | TIMESTAMP |
User | VARCHAR(255) |
Write all statements to <output_dir>/ddl.sql.
Step 9 — Write all files
Create the directory structure and write all files:
<output_dir>/
<app_name>/
META-INF/
MANIFEST.MF
application/
<app-uuid>.xml
group/
<admin-uuid>.xml
<viewer-uuid>.xml
recordType/
<record-uuid>.xml (one per record type)
webApi/
<api-uuid>.xml (one per endpoint)
ddl.sql
Step 10 — Present summary
═══════════════════════════════════════════════════════
Appian App Generated: <app_name>
═══════════════════════════════════════════════════════
Prefix: <PREFIX>
Record types: <N>
Web APIs: <M> (<G> GET, <P> POST)
Groups: 2 (Administrators, Users)
Output: <output_dir>/<app_name>/
Next steps:
1. Run the DDL in ddl.sql against your Appian data source
2. Import the <app_name>/ folder as an Appian package
3. Sync the record types in Appian Designer
4. Test the web APIs using the Appian API tester
═══════════════════════════════════════════════════════
Reference files
#[[file:schemas/api-contract.schema.json]]
#[[file:gitignore/appian-app-skillset/web-api-gen-context/Case Management (app export)/recordType/e5a92c16-4f98-456f-beb2-31cee9a0efc0.xml]]
#[[file:gitignore/appian-app-skillset/web-api-gen-context/Case Management - Generated APIs - 2026-04-08_1706/webApi/89a21248-c9ae-484c-bcb0-271876041c37.xml]]
#[[file:gitignore/appian-app-skillset/web-api-gen-context/Case Management (app export)/application/ebecd19a-1e11-4f62-9519-f08dbc9b3a2e.xml]]
#[[file:gitignore/appian-app-skillset/web-api-gen-context/Case Management (app export)/group/09b87e8a-a2fd-4f42-8130-6bd081c6e66c.xml]]
#[[file:gitignore/write-records-api.txt]]