Skip to main content
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": [...]
    }
  }
}
FieldTypeDescription
envobjectGlobal environment variables available to all workflows
before_allstringShell command run before any workflow
after_allstringShell command run after workflow completes (success or failure)
errorstringShell command run only on workflow failure
workflowsobjectMap 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"
    }
  ]
}
FieldTypeDescription
descriptionstringOptional description (shown in workflow list)
privatebooleanHide from workflow list (callable only via other workflows)
envobjectWorkflow-specific environment variables
stepsarrayOrdered 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"
}
FieldTypeRequiredDescription
runstringConditionalShell command to execute (mutually exclusive with workflow)
workflowstringConditionalName of another workflow to call (mutually exclusive with run)
namestringNoStep identifier (for debugging and JSON output)
ifstringNoOnly run if the environment variable exists and is non-empty
withobjectNoAdditional 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

  1. Runtime parameters (highest priority): asc workflow run example VAR:value
  2. Step-level with: {"with": {"VAR": "value"}}
  3. Workflow-level env: {"env": {"VAR": "value"}}
  4. Global env: Top-level env object
  5. 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:
asc workflow run release
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.

Output Format

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