Orchestrate Templated App Assets with Chains

Use a chain.json file to define an ordered graph of nodes to create, update, or delete the templated app assets in the subscriber org. Your chain files act as the installation script for the template, making sure that the correct assets exist based on the subscriber org and on app and asset dependencies. A node is an entry in definition.nodes, and its graph node type, such as DashboardUpsert, is the task it runs.

Learn how to add your chains to template-info.json, structure your chain files, choose node types, and meet security review requirements.

Add Chains to Your Template 

Each template requires a Create chain. OrgReadiness, Update, and Delete chains are optional. Without update or delete chains, the framework updates or deletes all template assets. Use a readiness chain when you need to check the subscriber org for prerequisites before creating or updating the templated app. To stop the templated app from being created or updated when a readiness check fails, fail the task. The log messages explain why the process failed.

Name the files anything you like, and map each one to its correct type in chainDefinitions.

Reference your chain files in template-info.json:

1"chainDefinitions": [
2  {
3    "type": "OrgReadiness",
4    "file": "readiness-chain.json"
5  },
6  {
7    "type": "Create",
8    "file": "create-chain.json"
9  },
10  {
11    "type": "Update",
12    "file": "update-chain.json"
13  },
14  {
15    "type": "Delete",
16    "file": "delete-chain.json"
17  }
18]

Chain File Structure 

Chain files contain the chain metadata and the definition of one or more nodes.

Each node definition has required graphNodeType, parameters, and sources attributes, and an optional runAs attribute. A root node has an empty sources array. Every other node lists at least one node it depends on, and any node in the chain can be a source.

Each chain.json file has these attributes:

AttributePathTypeRequiredDescription
name(top-level)StringYesA unique name for the chain.
label(top-level)StringNoA label for the chain.
dominoVariant(top-level)StringYesThe framework variant for the chain. For all chain files, this value must be sfdc_internal__UnifiedAnalyticsDominoVariant.
parameters(top-level)ObjectYesA collection of parameters for the chain. This is a placeholder for allowing chain-level payload attributes.
definition(top-level)ObjectYesThe definition of ordered nodes for the chain.
nodesdefinitionObjectYesA collection of one or more named nodes for the chain to run.
<node_name>definition.nodesObjectYesA unique key for the node object. Child nodes use this key in their sources attribute.
graphNodeTypedefinition.nodes.<node_name>ObjectYesThe graph node type metadata.
namedefinition.nodes.<node_name>.graphNodeTypeStringYesThe node type name. See Graph Node Types for valid values.
labeldefinition.nodes.<node_name>.graphNodeTypeStringNoA label for the graph node type.
parametersdefinition.nodes.<node_name>ObjectYesA collection of parameters for the node type. See each node type reference for the specific parameters.
sourcesdefinition.nodes.<node_name>ArrayYesThe parent sources for this node, if any. For a root node with no dependencies, use an empty array, []. For a child node, the array must list the <node_name> of each node that the child node depends on.
runAsdefinition.nodes.<node_name>StringNoThe user permissions for running the node. Valid values are CurrentUser and AutoProc. For most nodes, use CurrentUser, which runs the node as the user installing the app. For the ContentAssetUpsert node type, use AutoProc, which runs as a system user. Delete chains require CurrentUser. See Best Practices. The default value is AutoProc.

When node parameters include file attributes, the file paths resolve from the root of the template bundle. This sample bundle contains the files that the chain examples use:

1mytemplate/
2├── template-info.json
3├── create-chain.json
4├── readiness-chain.json
5├── csvs/
6|   └── customers-100.csv
7├── sms/
8|   └── mySemanticModel.json
9├── visualizations/
10|   └── myVisualization.json
11└── workspaces/
12    └── myWorkspace.json

Chain File Examples 

These example chain files show a simple one-node chain, a chain with ordered dependencies, and a readiness chain.

Simple Chain Example 

This example shows a chain file that uses one node to upload a CSV file. No other nodes are present in the chain.

1{
2  "name": "create_app_with_csv",
3  "label": "Create app with a CSV Upload",
4  "dominoVariant": "sfdc_internal__UnifiedAnalyticsDominoVariant",
5  "parameters": {},
6  "definition": {
7    "nodes": {
8      "update_csv": {
9        "graphNodeType": {
10          "name": "CSVUpsert",
11          "label": "Upload the customers CSV"
12        },
13        "parameters": {
14          "name": "CustomersCSV",
15          "file": "csvs/customers-100.csv"
16        },
17        "sources": [],
18        "runAs": "CurrentUser"
19      }
20    }
21  }
22}

Chain Ordering Example 

This example shows a chain file that uses sources dependencies to run multiple nodes in order. First, it creates a workspace, then a semantic model for the workspace, and then a visualization that uses the semantic model as a data source. The order matters. For example, don’t create a visualization before its supporting semantic model exists. The chain.json file encodes the sequencing explicitly.

Asset names use app context references, ${App.<AssetType>.<name>.Name}, so they’re unique to each installed app.

When WorkspaceUpsert creates a workspace, it can name the assets that the workspace contains, such as a semantic model.

Note

1{
2  "name": "create_app",
3  "label": "Create app with a workspace, semantic model, and visualization",
4  "dominoVariant": "sfdc_internal__UnifiedAnalyticsDominoVariant",
5  "parameters": {},
6  "definition": {
7    "nodes": {
8      "upsert_workspace": {
9        "graphNodeType": {
10          "name": "WorkspaceUpsert",
11          "label": "Create the workspace with a reference to the semantic model"
12        },
13        "parameters": {
14          "name": "${App.Workspaces.MyWorkspace.Name}",
15          "label": "My Workspace",
16          "file": "workspaces/myWorkspace.json",
17          "assets": [
18            {
19              "name": "${App.SemanticModels.MySemanticModel.Name}",
20              "assetType": "SemanticModel",
21              "usageType": "Referenced"
22            }
23          ]
24        },
25        "sources": [],
26        "runAs": "CurrentUser"
27      },
28      "upsert_sm": {
29        "graphNodeType": {
30          "name": "SemanticModelUpsert",
31          "label": "Create the semantic model"
32        },
33        "parameters": {
34          "name": "${App.SemanticModels.MySemanticModel.Name}",
35          "label": "My Semantic Model",
36          "file": "sms/mySemanticModel.json",
37          "capabilities": {
38            "editLocked": "true"
39          }
40        },
41        "sources": [ "upsert_workspace" ],
42        "runAs": "CurrentUser"
43      },
44      "upsert_visualization": {
45        "graphNodeType": {
46          "name": "VisualizationUpsert",
47          "label": "Create the visualization"
48        },
49        "parameters": {
50          "name": "${App.Visualizations.MyVisualization.Name}",
51          "label": "My Visualization",
52          "file": "visualizations/myVisualization.json",
53          "capabilities": {
54            "editLocked": "true"
55          }
56        },
57        "sources": [ "upsert_sm" ],
58        "runAs": "CurrentUser"
59      }
60    }
61  }
62}

Readiness Chain Example 

This example shows a readiness chain that performs four tasks:

  1. readiness_check1 verifies that the required org preferences are present
  2. template_requirement verifies that those preferences have the expected values
  3. readiness_check2 counts the rows in a Data 360 object
  4. template_requirement2 verifies that the row count is greater than 0

The readiness_check2 node sets valueOnError to -1. If the count fails, the node returns WARN_AND_CONTINUE, and template_requirement2 then fails the chain with a clear message.

1{
2  "name": "perform_readiness_checks",
3  "label": "Performs Readiness Checks",
4  "dominoVariant": "sfdc_internal__UnifiedAnalyticsDominoVariant",
5  "parameters": {},
6  "definition": {
7    "nodes": {
8      "readiness_check1": {
9        "graphNodeType": {
10          "name": "OrgPreferenceReadiness",
11          "label": "Verify that org preferences exist"
12        },
13        "parameters": {
14          "name": "OrgPrefCheck",
15          "preferenceNames": ["${Variables.preferenceToCheck1}", "${Variables.preferenceToCheck2}"]
16        },
17        "sources": [],
18        "runAs": "CurrentUser"
19      },
20      "template_requirement": {
21        "graphNodeType": {
22          "name": "TemplateRequirementEvaluation",
23          "label": "Verify the org preferences have the expected values"
24        },
25        "parameters": {
26          "condition": "${Readiness['OrgPrefCheck'][Variables.preferenceToCheck1]}",
27          "expression": "${Readiness['OrgPrefCheck'][Variables.preferenceToCheck2] == Variables.expectedValue}",
28          "successMessage": "Congratulations! The expected org preference [${Variables.preferenceToCheck2}] value matches!",
29          "failMessage": "Expected preference [${Variables.preferenceToCheck2}] value does not match. Expected: ${Variables.expectedValue}, Actual: ${Readiness['OrgPrefCheck'][Variables.preferenceToCheck2]}"
30        },
31        "sources": ["readiness_check1"],
32        "runAs": "CurrentUser"
33      },
34      "readiness_check2": {
35        "graphNodeType": {
36          "name": "DataCloudRowCountReadiness",
37          "label": "Verify there is an Id row in the Data 360 object"
38        },
39        "parameters": {
40          "name": "IdFieldRowCountCheck",
41          "object": "${Variables.dataCloudObject}",
42          "filters": [
43            {
44              "field": "Id__c",
45              "operator": "NotEqual",
46              "value": ""
47            }
48          ],
49          "valueOnError": -1
50        },
51        "sources": ["template_requirement"],
52        "runAs": "CurrentUser"
53      },
54      "template_requirement2": {
55        "graphNodeType": {
56          "name": "TemplateRequirementEvaluation",
57          "label": "Verify the ID row count is greater than 0"
58        },
59        "parameters": {
60          "expression": "${Readiness.IdFieldRowCountCheck > 0}",
61          "successMessage": "Congratulations! The expected Data 360 object has data!",
62          "failMessage": "Expected Data 360 object doesn't have data."
63        },
64        "sources": ["readiness_check2"],
65        "runAs": "CurrentUser"
66      }
67    }
68  }
69}

Graph Node Types 

Graph node types are the tasks you want your chain to run. Tasks include creating, updating, and deleting assets, and running readiness checks. Each supported graph node type has specific parameters. For parameters and example code, see the reference for each graph node type.

NameChain TypeDescription
AddExistingAssetToWorkspaceCreate, UpdateAdds an existing asset to an existing Tableau Next workspace.
AddToExistingWorkspaceCreate, UpdateAdds a list of assets to an existing Tableau Next workspace.
AppCountReadinessReadinessReturns a count of apps installed from this template in the subscriber org.
BaseTemplateInstalledReadinessReadinessValidates whether a specific base template is installed in the subscriber org.
BulkDeleteAssetsDeleteSpecifies the assets to delete when the app is deleted.
ContentAssetUpsertCreate, UpdateCreates scoped ContentAssets for reference in other templated assets, such as dashboards.
CSVUpsertCreate, UpdateUploads a CSV file used by data streams to produce data lake objects.
CustomFieldCreateCreate, UpdateCreates a custom field for a Salesforce object.
DashboardDeleteDeleteDeletes all Tableau Next dashboards or specific dashboards in a templated app.
DashboardUpsertCreate, UpdateCreates or updates a Tableau Next dashboard for a templated app.
DataCloudRowCountReadinessReadinessCounts the number of rows that a specific Data 360 object has in the subscriber org.
DataKitInstallCreate, UpdateInstalls a data kit in a target data space.
DataKitInstalledReadinessReadinessValidates whether a data kit is installed in a target data space.
DataspaceCreate, UpdateRetrieves a Data 360 data space to use as an app context token for assets that reference a data space.
ExtendsBaseSdmReadinessReadinessValidates whether a specific semantic model extends a base template’s semantic model.
FlowDefinitionUpsertCreate, UpdateCreates or updates a Salesforce Flow.
MetricUpsertCreate, UpdateCreates or updates a Tableau Next metric for a templated app.
OrgPreferenceReadinessReadinessValidates that the subscriber org has the correct preferences.
ReadinessChainRunReadinessRuns a readiness chain based on one or more conditions.
SemanticDataModelReadinessReadinessValidates that the data model objects referenced in a semantic model exist and have correct field mappings.
SemanticModelUpsertCreate, UpdateCreates or updates a semantic model for a templated app.
TemplatedDirectRestCreate, UpdateCalls Connect REST APIs directly and save the result in the app context.
TemplateRequirementEvaluationReadinessEvaluates template requirements in the subscriber org before a templated app is created.
VisualizationDeleteDeleteDeletes all Tableau Next visualizations or specific visualizations in a templated app.
VisualizationUpsertCreate, UpdateCreates or updates a Tableau Next visualization for a templated app.
WorkspaceUpsertCreate, UpdateCreates or updates a Tableau Next workspace for a templated app.

Handle Task Failures 

Depending on the node type, a failed task either stops the chain or logs a warning and lets the chain continue. Each node type reference lists its failure type.

If the chain fails, the app installation stops and the failure appears in the App Install logs. For more information, see Track Installations and Refreshes from the Monitor Tab.

Node Status TypeDescription
COMPLETEThe node completed and the chain continues.
SKIPPEDThe node was skipped and the chain continues.
FAIL_TERMINALThe chain stops, and downstream nodes don’t run.
WARN_AND_CONTINUEThe chain continues. If the node defines valueOnError, that value is written to the app context for downstream nodes.

Best Practices and Security Review Requirements 

Follow these practices so that your template installs safely and passes security review.

Best Practices 

  • Use ${App.<AssetType>.<name>.Name}, ${App.Dataspace.Name}, or ${Variables.x} for node parameters instead of hardcoded org-specific IDs and names.
  • Never hardcode asset names, such as "name": "BumpChart", in chains. Asset names resolve globally within the workspace and aren’t scoped to your template. A name collision can let your template overwrite or delete another installed template’s assets. Qualify names with the app ID or your namespace.

Security Review Requirements 

  • Using runAs: "AutoProc" on any node in a delete-chain.json is an automatic fail because it grants irreversible, system-level deletion power that isn’t scoped to what the installing user owns. Always set runAs: "CurrentUser" in all nodes in delete chains. If you omit runAs, the node runs as AutoProc, which fails security review in delete chains.
  • Using FlowDefinitionUpsert nodes triggers a mandatory manual security review. A packaged Flow can call Apex outside your namespace, perform CRM DML, or make callouts, all fully under your control as the template developer. If you include a Flow, be ready to document everything it does.
  • Using TemplateDirectRest nodes with any ${Variables.*} reference in the URL, endpoint, or host is an automatic fail because it’s a server-side request forgery (SSRF) risk. Disclose any hardcoded URLs in your template submission so that the reviewer can confirm that you own the domain.

See Also