Skip to content

Appendix G: A Technical Guide for Repository and Organization Administrators

Introduction

As Salesforce organizations scale their use of GitHub, CI/CD pipelines multiply. Individual project teams duplicate workflow logic, authentication steps drift out of sync, and a security policy change that should affect every repository ends up requiring dozens of separate pull requests. The answer is centralization: defining automation once, then composing and calling it across every repository in the organization.

GitHub Actions provides three distinct mechanisms for code reuse—reusable workflows, composite actions, and starter workflow templates—plus a special organizational repository (.github) that acts as the canonical source for sharing them. Each mechanism solves a different problem, and understanding when to reach for each one is the first step toward a clean, maintainable CI/CD architecture.

This appendix targets GitHub organization administrators and senior contributors managing Salesforce DX (SFDX) projects. Code examples assume familiarity with the Salesforce CLI (sf) and standard SFDX project layouts.

The GitHub Actions Component Landscape

Before diving into implementation, it is worth establishing a clear mental model of the components and how they relate to one another.

┌───────────────────────────────────────────────────────────┐
│                   Calling Repository                      │
│                                                           │
│  .github/workflows/pr-validation.yml  ← Caller Workflow   │
│       │                                                   │
│       ├── uses: org/.github/.github/workflows/deploy.yml  │  ← Reusable Workflow (a job)
│       │                                                   │
│       └── steps:                                          │
│             - uses: org/.github/.github/actions/sf-auth   │  ← Composite Action (a step in a job)
└───────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────┐
│              org/.github (Shared Repository)              │
│                                                           │
│  .github/workflows/        ← Reusable Workflows           │
│  .github/actions/          ← Composite Actions            │
│  workflow-templates/       ← Starter Workflow Templates   │
└───────────────────────────────────────────────────────────┘

The three building blocks:

  • Reusable workflows are complete workflow files that can be called as an entire job. They define their own runners and job structure. Think of them as pipeline templates.
  • Composite actions are collections of steps bundled into a single action and invoked as a single step within a job. Think of them as shared task functions.
  • Starter workflow templates are pre-built workflow files that GitHub surfaces in the "Actions" tab of any repository in the organization, giving developers a governed starting point rather than a blank page.

The Organizational .github Repository

GitHub treats a repository named .github within your organization as a special shared resource. It is the central hub for all cross-repository automation assets.

Repository Structure (Subset of full list)

<my-org>/.github/
├── .github/
│   ├── workflows/                # Reusable workflows (called with `uses:`)
│   │   ├── sf-prettier-verify.yml
│   │   ├── sf-code-analyzer.yml
│   │   ├── sf-apex-tests-pr.yml
│   │   ├── sf-validate-deployment.yml
│   │   └── …                     # (see Appendix A.2 for the full list)
│   └── actions/                  # Composite actions (called as steps)
│       ├── setup-sf/
│       │   └── action.yml
│       ├── sf-auth-jwt/
│       │   └── action.yml
│       ├── sf-auth-sfdx-url/
│       │   └── action.yml
│       └── post-pr-comment/
│           └── action.yml
└── workflow-templates/           # Starter templates (shown in the Actions UI)
    ├── sf-pr-checks.yml
    └── sf-pr-checks.properties.json

Note: The distinction between workflow-templates/ (at the root) and .github/workflows/ (inside the .github subdirectory) often causes confusion. Templates live at the repository root and are surfaced in the UI; reusable workflows live inside .github/workflows/ and are called programmatically from other workflows.

Access Control

The visibility of the .github repository determines who can use assets from it:

  • Public .github repository: Reusable workflows and composite actions are callable from any repository (including those outside your organization).
  • Internal .github repository (GitHub Enterprise): Accessible from any internal or private repository within the enterprise.
  • Private .github repository: Accessible only from repositories within the same organization. This is the recommended setting for most enterprise Salesforce teams.

Additionally, at the organization level, administrators can restrict Actions to only allow workflows and reusable workflows from within the organization. This prevents developers from accidentally pulling in unvetted third-party actions (GitHub Docs: Disabling or limiting GitHub Actions for your organization).

Reusable Workflows

A reusable workflow is a standard GitHub Actions YAML file that exposes a workflow_call trigger. Any other workflow in the organization can invoke it as a complete job, passing in typed inputs and secrets.

Anatomy of a Reusable Workflow

# <my-org>/.github/.github/workflows/sf-validate-deployment.yml
# (abbreviated - see Appendix A.2.11 for the full reference implementation)

name: Metadata Validation

on:
  workflow_call:
    inputs:
      test-level:
        description: 'Test level for validation run'
        required: false
        type: string
        default: 'RunLocalTests'
      wait-minutes:
        description: 'Minutes to wait for validation to complete'
        required: false
        type: string
        default: '60'
      artifact-name:
        description: 'Name of the delta package artifact to download'
        required: false
        type: string
        default: 'delta-package'
    secrets:
      SFDX_AUTH_URL_VALIDATION:
        description: 'SFDX auth URL for the target validation org'
        required: true

jobs:
  validate-deploy:
    name: Metadata Validation
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to validation org
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL_VALIDATION }}
          alias: validation-org

      - name: Download delta package
        uses: actions/download-artifact@v8
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/

      - name: Validate delta deployment
        run: |
          sf project deploy start \
            --dry-run \
            --target-org validation-org \
            --manifest delta/package/package.xml \
            --test-level ${{ inputs.test-level }} \
            --wait ${{ inputs.wait-minutes }}

The key elements of a reusable workflow definition:

  • on.workflow_call — the trigger that makes this file callable from other workflows.
  • inputs — typed parameters (string, boolean, number) with optional defaults.
  • secrets — explicitly declared secrets the called workflow needs. These are separate from inputs and cannot be referenced in with: expressions on the caller side.
  • outputs — values surfaced back to the caller, mapped from job outputs.

Calling a Reusable Workflow

# any-salesforce-repo/.github/workflows/pr-validation.yml

name: PR Validation

on:
  pull_request:
    branches: [main]

jobs:
  validate:
    uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2
    with:
      test-level: RunLocalTests
    secrets:
      SFDX_AUTH_URL_VALIDATION: ${{ secrets.SFDX_AUTH_URL_PROD }}

The uses: key references the reusable workflow using the format {owner}/{repo}/{path}@{ref}. The @{ref} component can be a branch name, tag, or commit SHA. Using a specific tag (e.g., @v2) is strongly recommended in production, as it pins the behavior of the called workflow and prevents unexpected breakage from upstream changes.

Passing Secrets: Explicit vs. inherit

GitHub provides two mechanisms for forwarding secrets to a called workflow.

Explicit passing (recommended for clarity):

jobs:
  apex-tests:
    uses: <my-org>/.github/.github/workflows/sf-apex-tests-pr.yml@v2
    secrets:
      SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL_PROD }}
      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Implicit inheritance (convenient, use with caution):

jobs:
  apex-tests:
    uses: <my-org>/.github/.github/workflows/sf-apex-tests-pr.yml@v2
    secrets: inherit

With secrets: inherit, every secret available to the caller is automatically forwarded to the called workflow. This is convenient in same-organization calls, but it means the called workflow has access to secrets it may not need—a relevant consideration in least-privilege security models.

Important: Secrets are only passed to directly called workflows. In a chain A → B → C, workflow C receives secrets from A only if B explicitly forwards them. secrets: inherit at each hop is required for full propagation.

Nesting and Limits

As of late 2025, GitHub raised the nesting limit to 10 levels of reusable workflow calls, and a single workflow run can call up to 50 reusable workflows in total (GitHub Changelog: New releases for GitHub Actions, November 2025). In practice, deep nesting should be treated as an architectural smell; two or three levels is typically the maximum useful depth.

A calling job that uses uses: to invoke a reusable workflow cannot contain additional steps:. The entire job is delegated to the called workflow. If you need steps before or after the delegated logic within the same job, use a composite action instead.

Composite Actions

A composite action bundles multiple steps into a single unit that is consumed as a uses: step within a job—not as a job itself. Where a reusable workflow replaces a job, a composite action replaces a step.

Anatomy of a Composite Action

Each composite action lives in its own directory and is defined in an action.yml file.

# <my-org>/.github/.github/actions/sf-auth-sfdx-url/action.yml

name: 'Authenticate to Salesforce (SFDX URL)'
description: 'Authenticates to a Salesforce org using an SFDX auth URL.'

inputs:
  sfdx-url:
    description: 'The SFDX auth URL for the target org.'
    required: true
  alias:
    description: 'The local alias to assign to the authenticated org.'
    required: false
    default: target-org

runs:
  using: composite
  steps:
    - name: Write SFDX URL to temporary file
      shell: bash
      run: echo "${{ inputs.sfdx-url }}" > /tmp/sfdx-auth-url.txt

    - name: Validate SFDX URL file
      shell: bash
      run: |
        FILE_SIZE=$(wc -c < /tmp/sfdx-auth-url.txt | tr -d ' ')
        if [ "$FILE_SIZE" -le 1 ]; then
          echo "::error::SFDX URL is empty or not set. Ensure the secret is configured correctly."
          rm -f /tmp/sfdx-auth-url.txt
          exit 1
        fi

    - name: Authenticate via SFDX URL
      shell: bash
      run: |
        sf org login sfdx-url \
          --sfdx-url-file /tmp/sfdx-auth-url.txt \
          --alias "${{ inputs.alias }}" \
          --set-default

    - name: Remove SFDX URL file
      shell: bash
      if: always()
      run: rm -f /tmp/sfdx-auth-url.txt

Key structural requirements:

  • runs.using: 'composite' — declares this as a composite action (as opposed to a JavaScript or Docker action).
  • Every run step within a composite action must declare a shell. This is a requirement unique to composite actions.
  • Inputs are referenced via ${{ inputs.<name> }}. Unlike reusable workflows, inputs to composite actions are strings only: they have no typed boolean or number variants. Callers must pass 'true' or 'false' as strings and handle coercion in the action's shell logic. The setup-sf action in the reference implementation (Appendix A.3.1) is a concrete example: its install-code-analyzer and install-sfdx-git-delta inputs are compared as strings (if: inputs.install-code-analyzer == 'true').

Calling a Composite Action

# any-salesforce-repo/.github/workflows/deploy.yml

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Authenticate to sandbox
        uses: <my-org>/.github/.github/actions/sf-auth-sfdx-url@v1
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: 'ci-sandbox'

      - name: Deploy
        run: |
          sf project deploy start \
            --source-dir force-app \
            --target-org ci-sandbox \
            --wait 30

Notice that the composite action is a step, and additional steps freely appear before and after it in the same job. This is the key ergonomic difference from reusable workflows.

Secrets in Composite Actions

Composite actions do not have an on.workflow_call.secrets block. Secrets must be passed as inputs. This is an intentional design: the calling workflow holds the secret context and passes only what the action needs. The action receives it as an input string and must handle it accordingly (e.g., writing to a temp file, not echoing to logs).

- uses: <my-org>/.github/.github/actions/sf-auth-sfdx-url@v1
  with:
    sfdx-url: ${{ secrets.SFDX_AUTH_URL }} # Secret passed as input
    alias: 'my-sandbox'

Starter Workflow Templates

Starter workflow templates solve a different problem: discoverability and governance at the moment a developer creates a new repository. Instead of starting from scratch, developers see your organization's approved templates in the GitHub "Actions" tab.

This functionality may be irrelevant at strictly-governed organizations, such as federal gencies, where all workflows must be approved, even to create a workflow.

In a slightly relaxed situation, template workflows may be pre-approved initially and able to be installed by any repository editor in a feature branch, and then any edits to those files would require organization administrator approval.

Creating a Starter Template

Templates live in the workflow-templates/ directory at the root of the org .github repository (not inside .github/workflows/). Each template requires two files: the workflow YAML and a .properties.json metadata file with the same base name.

org/.github/
└── workflow-templates/
    ├── salesforce-pr-validation.yml
    ├── salesforce-pr-validation.properties.json
    ├── salesforce-deploy.yml
    └── salesforce-deploy.properties.json

The workflow file:

# workflow-templates/salesforce-pr-validation.yml

name: Salesforce PR Validation

on:
  pull_request:
    branches: [main]

jobs:
  validate:
    uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2
    with:
      test-level: RunLocalTests
    secrets:
      SFDX_AUTH_URL_VALIDATION: ${{ secrets.SFDX_AUTH_URL_PROD }}

The properties file:

{
    "name": "Salesforce PR Validation",
    "description": "Validates a Salesforce DX pull request against the CI sandbox using a check-only deployment.",
    "iconName": "salesforce",
    "categories": ["Salesforce", "Deployment"]
}

Templates Calling Reusable Workflows

As illustrated above, a starter template can itself call a reusable workflow using uses:. This is a powerful composition pattern: the template gives every new repository a working pipeline on day one, while the actual logic lives in a single, centrally maintained reusable workflow. When the deployment logic needs to change (e.g., to add a new test level flag), you update the reusable workflow in one place, and every repository automatically picks up the change on their next run.

Non-Public Template Support (2025)

Prior to late 2025, starter workflow templates required the .github repository to be public. GitHub now supports templates from non-public repositories (GitHub Changelog: YAML anchors and non-public workflow templates). If the .github repository is internal, all internal and private repositories in the enterprise can see and use the templates. If it is private, only repositories within the same organization can use them. This is a significant improvement for enterprise teams that cannot expose their pipeline logic publicly.

Combining the Components: An Organization-Wide Strategy

The three components are not mutually exclusive—they are designed to be layered. A well-structured organization uses all three in concert:

Developer creates new Salesforce repo
  Selects "Salesforce PR Validation" starter template
  from Actions tab (Starter Workflow Template)
  Template calls the org's canonical validate workflow
  uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2
  (Reusable Workflow)
  Reusable workflow uses composite actions for shared steps
  uses: ./.github/actions/sf-auth-sfdx-url
  (Composite Action)

Repository Versioning Strategy

When the .github repository stores both reusable workflows and composite actions, a single git tag covers all of them. This simplifies versioning but means a change to one action bumps the version for everything in the repo.

A common pattern is to maintain a vN floating tag (e.g., v2) alongside immutable release tags (e.g., v2.3.1). Callers that use @v2 receive non-breaking updates automatically, while callers that pin to @v2.3.1 get complete stability.

# After merging a non-breaking change:
git tag -f v2
git push origin v2 --force

# For a new major version with breaking changes:
git tag v3.0.0
git push origin v3.0.0
git tag v3
git push origin v3

Callers targeting org-wide standards reference @v2:

uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2

Organization Policy Enforcement

GitHub organization administrators can enforce that all repositories only use actions and reusable workflows from within the organization. This is configured at Organization Settings → Actions → General → Allow actions and reusable workflows. Selecting "Allow actions created by GitHub" plus your own organization prevents any external action from running without explicit approval, which is an important control for regulated Salesforce environments handling financial or healthcare data.

Salesforce SFDX: Putting It Into Practice

The following example represents a realistic multi-repository Salesforce organization using all three components.

Shared Repository Structure (<my-org>/.github)

The following structure mirrors the actual reference implementation whose full contents appear in Appendix A. The inner .github/workflows/ directory holds both the caller workflows (A.1) and the reusable workflow implementations they invoke (A.2). The inner .github/actions/ directory holds the composite actions that the reusable workflows call as steps (A.3).

<my-org>/.github/
├── workflow-templates/                   # Starter templates surfaced in the Actions UI
│   ├── sf-pr-checks.yml
│   ├── sf-pr-checks.properties.json
│   ├── sf-scheduled-checks.yml
│   └── sf-scheduled-checks.properties.json
└── .github/
    ├── workflows/                        # Caller and reusable workflows
    │   ├── ci.yml                        # Caller: push baseline (A.1.1)
    │   ├── pr-prod.yml                   # Caller: PRs to main (A.1.2)
    │   ├── pr-staging.yml                # Caller: PRs to staging/uat/integration (A.1.3)
    │   ├── scheduled-drift-detection.yml # Caller: weekly surveillance (A.1.4)
    │   ├── scheduled-test-runner.yml     # Caller: nightly tests (A.1.5)
    │   ├── sf-prettier-verify.yml        # Reusable: formatting gate (A.2.1)
    │   ├── sf-secret-scan.yml            # Reusable: secret scanning (A.2.2)
    │   ├── sf-semver-check.yml           # Reusable: PR title convention (A.2.3)
    │   ├── sf-destructive-check.yml      # Reusable: destructive change detection (A.2.4)
    │   ├── sf-generate-delta.yml         # Reusable: delta package generation (A.2.5)
    │   ├── sf-code-analyzer.yml          # Reusable: Code Analyzer (A.2.6)
    │   ├── sf-jest-tests.yml             # Reusable: LWC Jest (A.2.7)
    │   ├── sf-apex-tests-pr.yml          # Reusable: PR-scoped Apex tests (A.2.8)
    │   ├── sf-apex-tests.yml             # Reusable: full-org Apex tests (A.2.9)
    │   ├── sf-apex-compilation.yml       # Reusable: full-org compilation check (A.2.10)
    │   ├── sf-validate-deployment.yml    # Reusable: metadata validation (A.2.11)
    │   ├── sf-drift-detection.yml        # Reusable: drift detection (A.2.12)
    │   ├── sf-notify-slack.yml           # Reusable: Slack notification (A.2.13)
    │   └── release-please.yml            # Reusable: release management (A.2.14)
    └── actions/                          # Composite actions
        ├── setup-sf/                     # Node, SF CLI, Code Analyzer, sfdx-git-delta (A.3.1)
        │   └── action.yml
        ├── sf-auth-jwt/                  # JWT bearer flow auth (A.3.2)
        │   └── action.yml
        ├── sf-auth-sfdx-url/             # SFDX URL auth (A.3.3)
        │   └── action.yml
        ├── apex-list/                    # Enumerate ApexClass and ApexTrigger (A.3.4)
        │   └── action.yml
        ├── apex-test-report/             # Parse sf apex run test JSON (A.3.5)
        │   └── action.yml
        ├── sf-code-analyzer-report/      # Parse Code Analyzer JSON (A.3.6)
        │   └── action.yml
        └── post-pr-comment/              # Post or update PR comment (A.3.7)
            └── action.yml

The delta package in this architecture is produced by the sf-generate-delta.yml reusable workflow (A.2.5), not a composite action. That workflow runs sf sgd source delta as a job, uploads the result as an artifact, and exposes an artifact-uploaded output. Downstream gates (sf-code-analyzer.yml, sf-apex-tests-pr.yml, sf-jest-tests.yml, sf-validate-deployment.yml) each download the artifact by name rather than regenerating the delta independently. Keeping delta generation in a job rather than a composite action lets multiple downstream jobs consume the same delta in parallel without re-running the underlying git diff.

Stitching the Pieces Together

The next four code blocks walk through how a caller workflow invokes a reusable workflow, how the reusable workflow calls composite actions as steps, and how a project repository consumes the whole chain.

The caller workflow is the trigger-bound entry point. In a project repository, it responds to a GitHub event and composes reusable workflows from the organization .github repository. From the reference implementation (pr-prod.yml, A.1.2), a simplified excerpt shows the pattern:

# salesforce-project-repo/.github/workflows/pr-prod.yml

name: CI on PR (prod)

on:
  pull_request:
    branches: [main]
    types: [opened, synchronize, ready_for_review]

jobs:
  generate-delta:
    name: Delta Package
    uses: <my-org>/.github/.github/workflows/sf-generate-delta.yml@v2

  apex-tests:
    name: Apex Tests
    needs: generate-delta
    if: needs.generate-delta.outputs.artifact-uploaded == 'true'
    uses: <my-org>/.github/.github/workflows/sf-apex-tests-pr.yml@v2
    secrets:
      SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL_PROD }}
      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

  validate-deploy:
    name: Validation
    needs: [generate-delta, apex-tests]
    if: needs.generate-delta.outputs.artifact-uploaded == 'true' && !failure() && !cancelled()
    uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2
    secrets:
      SFDX_AUTH_URL_VALIDATION: ${{ secrets.SFDX_AUTH_URL_PROD }}

Three layers are visible: the caller composes jobs, each job delegates to a reusable workflow via uses:, and the reusable workflows consume secrets passed explicitly from the caller. The needs: and if: conditions express dependency ordering and short-circuit on an empty delta so gates skip cleanly when no Salesforce metadata changed.

The reusable workflow implements a single quality gate. Its job calls composite actions for repeated setup steps. From sf-apex-tests-pr.yml (A.2.8), the job steps collapse to:

# <my-org>/.github/.github/workflows/sf-apex-tests-pr.yml

jobs:
  apex-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf # A.3.1
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to CI sandbox
        uses: ./.github/actions/sf-auth-sfdx-url # A.3.3
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: ci-org

      - name: Download delta package
        uses: actions/download-artifact@v8
        with:
          name: delta-package
          path: delta/

      # ... sf apex run test, apex-list, apex-test-report, post-pr-comment ...

Every step that wraps more than one command is a composite action. setup-sf replaces what would otherwise be five steps (Node, npm ci, CLI install, Code Analyzer plugin, sfdx-git-delta plugin) with one invocation that accepts toggles for the optional plugins. sf-auth-sfdx-url replaces the three-step pattern of writing the secret to a temp file, authenticating, and cleaning up. The net effect: every reusable workflow that connects to an org has the same shape, and authentication behavior changes in one place when it changes at all.

The composite action is the leaf of this call graph. It contains steps, not jobs, and runs in the caller job's runner context. The sf-auth-sfdx-url action shown in full in Appendix A.3.3 is a representative example: three run steps handle file write, CLI login, and cleanup, with an if: always() on the cleanup step to ensure the secret file never survives a failed login.

Why This Split Matters

Dividing the system into caller workflows, reusable workflows, and composite actions is not organizational hygiene for its own sake: each layer enables a specific kind of change without disturbing the others.

  • Adding a new quality gate is a reusable-workflow change. Create sf-<gate>.yml in the organization .github repository, add a job to pr-prod.yml and pr-staging.yml that calls it, and every consuming project inherits the gate on its next pipeline run.
  • Onboarding a new environment (e.g. a pre-production hotfix branch) is a caller-workflow change. Add pr-hotfix.yml that reuses the same gate chain with a different SFDX_AUTH_URL_HOTFIX secret. The reusable workflows do not change.
  • Switching authentication mechanisms (e.g. from SFDX auth URL to JWT bearer flow) is a composite-action change. Swap sf-auth-sfdx-url for sf-auth-jwt in the reusable workflows that need it, update the consuming caller secrets, and the gate logic is untouched.

Each layer has a distinct owner, a distinct change cadence, and a distinct blast radius. The combination is what makes the architecture sustainable at scale.

Key Differences at a Glance

Dimension Reusable Workflow Composite Action Starter Template
Defined in .github/workflows/*.yml with on: workflow_call action.yml with runs.using: composite workflow-templates/*.yml
Called with uses: in a job uses: in a step Selected from the Actions UI
Can contain Multiple jobs, multiple runners Steps only, single runner context Any valid workflow syntax
Secrets Declared in on.workflow_call.secrets or secrets: inherit Passed as inputs (string) Resolved at call time in calling workflow
Outputs Declared in on.workflow_call.outputs Declared in outputs: N/A
Max nesting 10 levels (as of late 2025) 10 levels 1 level (the template itself)
Can call reusable workflows? Yes No Yes
Versioning @{ref} in uses: @{ref} in uses: Copied into the target repo at creation
Runner defined by The called workflow The calling job The template itself

References