Workflows let you define reusable, multi-step automation sequences in a declarative JSON file. Think of workflows as lightweight CI/CD pipelines for App Store Connect tasks.
Quick Start
Create a workflow file at .asc/workflow.json:
{
"workflows": {
"beta": {
"description": "Distribute latest build to TestFlight beta group",
"steps": [
"asc builds list --app $APP_ID --limit 1 --sort -uploadedDate",
{
"name": "add_to_group",
"run": "asc builds add-groups --build $BUILD_ID --group $GROUP_ID"
}
]
}
}
}
Run it:
asc workflow run beta BUILD_ID:abc123 GROUP_ID:xyz789
Workflow File Structure
Workflows are defined in .asc/workflow.json (or a custom path with --file).
Top-Level Schema
{
"env": {
"APP_ID": "123456789",
"VERSION": "1.0.0"
},
"before_all": "asc auth status",
"after_all": "echo workflow_done",
"error": "echo workflow_failed",
"workflows": {
"workflow_name": {
"description": "...",
"steps": [...]
}
}
}
| Field | Type | Description |
|---|
env | object | Global environment variables available to all workflows |
before_all | string | Shell command run before any workflow |
after_all | string | Shell command run after workflow completes (success or failure) |
error | string | Shell command run only on workflow failure |
workflows | object | Map of named workflows |
Workflow Schema
{
"description": "Human-readable description",
"private": false,
"env": {
"BUILD_ID": ""
},
"steps": [
"asc apps list",
{
"name": "step_name",
"run": "asc builds list --app $APP_ID",
"if": "BUILD_ID",
"with": {
"NOTE": "custom value"
},
"workflow": "other_workflow"
}
]
}
| Field | Type | Description |
|---|
description | string | Optional description (shown in workflow list) |
private | boolean | Hide from workflow list (callable only via other workflows) |
env | object | Workflow-specific environment variables |
steps | array | Ordered list of steps to execute |
Step Schema
Steps can be short form (bare string) or long form (object):
// Short form
"asc apps list"
// Long form
{
"name": "list_apps",
"run": "asc apps list --output json",
"if": "VARIABLE_NAME",
"with": {"KEY": "value"},
"workflow": "sub_workflow_name"
}
| Field | Type | Required | Description |
|---|
run | string | Conditional | Shell command to execute (mutually exclusive with workflow) |
workflow | string | Conditional | Name of another workflow to call (mutually exclusive with run) |
name | string | No | Step identifier (for debugging and JSON output) |
if | string | No | Only run if the environment variable exists and is non-empty |
with | object | No | Additional environment variables for this step only |
A step must have either run or workflow, but not both.
Environment Variables
Workflows support environment variable expansion using $VAR or ${VAR} syntax:
{
"env": {
"APP_ID": "123456789",
"VERSION": "1.0.0"
},
"workflows": {
"example": {
"steps": [
"asc apps get --id $APP_ID",
"asc versions list --app ${APP_ID} --filter[versionString]=$VERSION"
]
}
}
}
Variable Precedence
- Runtime parameters (highest priority):
asc workflow run example VAR:value
- Step-level
with: {"with": {"VAR": "value"}}
- Workflow-level
env: {"env": {"VAR": "value"}}
- Global
env: Top-level env object
- Process environment (lowest priority): Inherited from shell
Runtime Parameters
Pass parameters when running a workflow:
asc workflow run beta \
BUILD_ID:abc123 \
GROUP_ID:xyz789 \
APP_ID:123456789
Parameters follow the format KEY:VALUE. They override all other variable sources.
Conditional Steps
Use the if field to skip steps when a variable is unset or empty:
{
"steps": [
{
"name": "upload_build",
"run": "asc builds upload --path app.ipa"
},
{
"name": "submit_build",
"if": "SUBMIT_FOR_REVIEW",
"run": "asc submit create --app $APP_ID --version $VERSION --build $BUILD_ID --confirm"
}
]
}
Skip the submission step:
Include the submission step:
asc workflow run release SUBMIT_FOR_REVIEW:true
Calling Other Workflows
Workflows can call other workflows using the workflow field:
{
"workflows": {
"preflight": {
"private": true,
"description": "Pre-deployment checks",
"steps": [
"asc auth status",
"asc apps get --id $APP_ID"
]
},
"deploy": {
"description": "Deploy to TestFlight",
"steps": [
{
"workflow": "preflight",
"with": {"NOTE": "Running preflight checks"}
},
"asc builds upload --path app.ipa"
]
}
}
}
Run the parent workflow:
asc workflow run deploy APP_ID:123456789
The preflight workflow is called first, with access to the NOTE variable.
Private workflows ("private": true) are hidden from asc workflow list but can be called by other workflows.
Hooks
Workflows support lifecycle hooks at the definition level:
before_all
Runs once before any workflow:
{
"before_all": "asc auth status",
"workflows": {...}
}
Use for authentication checks, environment validation, or setup tasks.
after_all
Runs once after the workflow completes (success or failure):
{
"after_all": "echo workflow_done",
"workflows": {...}
}
Use for cleanup, notifications, or logging.
error
Runs only on workflow failure:
{
"error": "echo workflow_failed && curl -X POST https://hooks.slack.com/...",
"workflows": {...}
}
Use for error notifications or rollback logic.
Hooks run via bash (with pipefail) or sh. At least one shell must be in PATH.
Workflow output is JSON-only on stdout:
asc workflow run beta BUILD_ID:abc123 GROUP_ID:xyz789 --pretty
{
"workflow": "beta",
"steps": [
{
"name": "step_1",
"command": "asc builds list --app $APP_ID --limit 1 --sort -uploadedDate",
"exit_code": 0
},
{
"name": "add_to_group",
"command": "asc builds add-groups --build abc123 --group xyz789",
"exit_code": 0
}
],
"before_all": {
"command": "asc auth status",
"exit_code": 0
},
"after_all": {
"command": "echo workflow_done",
"exit_code": 0
},
"success": true
}
Step and hook command output streams to stderr to keep stdout machine-parseable.
Commands
workflow run
Run a named workflow:
asc workflow run <name> [KEY:VALUE ...] [flags]
Flags:
--file - Path to workflow.json (default: .asc/workflow.json)
--dry-run - Preview steps without executing
--pretty - Pretty-print JSON output
Example:
asc workflow run beta \
BUILD_ID:abc123 \
GROUP_ID:xyz789 \
--dry-run \
--pretty
workflow list
List available workflows:
asc workflow list [flags]
Flags:
--file - Path to workflow.json
--all - Include private workflows
--pretty - Pretty-print JSON output
Output:
[
{
"name": "beta",
"description": "Distribute latest build to TestFlight beta group",
"private": false,
"step_count": 2
},
{
"name": "release",
"description": "Submit a version for App Store review",
"private": false,
"step_count": 3
}
]
workflow validate
Validate workflow file for errors and cycles:
asc workflow validate [flags]
Flags:
--file - Path to workflow.json
--pretty - Pretty-print JSON output
Output:
{
"valid": true,
"errors": []
}
Or on error:
{
"valid": false,
"errors": [
{
"workflow": "beta",
"message": "step 2: missing 'run' or 'workflow' field"
},
{
"workflow": "release",
"message": "circular dependency detected: release -> preflight -> release"
}
]
}
Security Considerations
Workflows intentionally execute arbitrary shell commands. Only run workflow files you trust.
Best Practices
- Review workflow files before running them, especially with
--file
- Avoid running workflows from untrusted sources (e.g., unreviewed PRs)
- Be careful with secrets - steps inherit your process environment
- In CI, use secret masking - workflows may log environment variables
- Use
workflow validate to check structure, but validation does not check safety
CI/CD Considerations
# GitHub Actions example
- name: Run workflow
run: asc workflow run beta BUILD_ID:${{ secrets.BUILD_ID }}
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
Avoid running workflows on untrusted pull requests with access to secrets or tokens.
Examples
TestFlight Distribution
{
"env": {
"APP_ID": "123456789"
},
"workflows": {
"beta": {
"description": "Distribute build to TestFlight beta group",
"env": {
"BUILD_ID": "",
"GROUP_ID": ""
},
"steps": [
{
"name": "list_builds",
"run": "asc builds list --app $APP_ID --sort -uploadedDate --limit 5"
},
{
"name": "list_groups",
"run": "asc testflight beta-groups list --app $APP_ID --limit 20"
},
{
"name": "add_build_to_group",
"if": "BUILD_ID",
"run": "asc builds add-groups --build $BUILD_ID --group $GROUP_ID"
}
]
}
}
}
Run:
asc workflow run beta BUILD_ID:abc123 GROUP_ID:xyz789
App Store Submission
{
"workflows": {
"release": {
"description": "Submit version for App Store review",
"steps": [
{
"workflow": "preflight",
"with": {"NOTE": "running preflight checks"}
},
{
"name": "submit",
"run": "asc submit create --app $APP_ID --version $VERSION --build $BUILD_ID --confirm"
}
]
},
"preflight": {
"private": true,
"description": "Pre-submission checks",
"steps": [
"asc auth status",
"asc apps get --id $APP_ID",
"asc builds get --id $BUILD_ID"
]
}
}
}
Run:
asc workflow run release \
APP_ID:123456789 \
VERSION:1.0.0 \
BUILD_ID:abc123