SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Consume and troubleshoot workflows

GitHub Actions Workflow Templates, Reusable Workflows and Composite Actions (GH-200)

17 min readGH-200 · Consume and troubleshoot workflowsUpdated

A GitHub Actions workflow template (formerly a starter workflow) is a workflow file that GitHub copies into a repository as a starting point, after which the copy is independent of the template. This lesson covers the GitHub Actions (GH-200) skills for consuming shared automation: using public and organization templates, including non-public ones, and how their metadata and visibility work; calling reusable workflows by job-level uses and choosing a ref; what a calling job may contain; access, nesting, permissions and context rules; consuming composite actions; telling the three sharing mechanisms apart; and the difference between disabling and deleting a workflow.

What you’ll learn
  • Choose between a workflow template, a reusable workflow and a composite action from a stated requirement
  • Create a workflow from public or organization templates and customise the independent copy
  • Explain where organization templates live, what their .properties.json metadata does, and which repositories can see non-public templates
  • Call a reusable workflow with the correct job-level uses path and ref, and pick a ref that matches the stability required
  • Write a valid calling job, including a matrix fan-out and environments supplied through inputs
  • Troubleshoot access, nesting, permission and context problems in reusable workflow chains and composite actions
  • Contrast disabling, deleting and automatic disabling of workflows, and use the UI, GitHub CLI or REST API to disable and enable them

Workflow templates vs reusable workflows vs composite actions

GitHub Actions gives you three ways to share automation, and the difference is what gets shared and whether it stays linked to the source. A workflow template (formerly called a starter workflow) is copied into a repository once and is then independent. A reusable workflow is a central workflow file with an on: workflow_call trigger that other workflows call from a job, so every caller runs the current version of the ref it names. A composite action is a central bundle of steps that a job runs as one step, on its own runner.

Workflow templateReusable workflowComposite action
What is sharedA whole workflow file, as a scaffoldOne or more complete jobsA sequence of steps
How it is usedCopied into .github/workflows when a workflow is createdjobs.<job_id>.uses in a calling jobsteps[*].uses in an existing job
Source updates reach users?No — the copy is independent after creationYes, on the next run of the ref the caller namesYes, on the next run of the ref the step names
RunnerWhatever the copied file saysEach called job sets its own runs-onThe calling job's runner and workspace
SecretsNormal workflow secretsPassed with secrets: or secrets: inheritNo secrets context; pass values in as inputs
LogsNormal workflow logsEvery job and step logged separately, in real timeShown as one step of the calling job
MarketplaceNoNoYes, can be published as an action

Choose by the requirement, not by the name. If teams need a starting point they will then change freely, use a template. If many repositories must pick up a fix to a whole pipeline without anyone editing them, or the shared work must run on a different runner from the team's own jobs, use a reusable workflow. If the shared logic must run inside an existing job, on files that earlier steps produced, without uploading artifacts, use a composite action.

Using workflow templates (starter workflows)

You use a workflow template from the repository's Actions tab: choose New workflow, pick a template, select Configure, edit the YAML in the editor and commit it. GitHub suggests public templates (maintained in the actions/starter-workflows repository) that fit the repository's languages and files, and lists templates your organization has published in their own section.

The result is an ordinary workflow file committed to .github/workflows in your repository. From that moment it is yours:

  • Customise the copy, not the template. To change one repository (a different Node.js version, an extra job), edit the workflow file in that repository. Editing the template in the organization's .github repository would change it for future copies only, and other teams' choices would be overridden next time they create from it.
  • Template changes never propagate. If the platform team later adds a step to the template, existing workflows created from it stay exactly as they were until someone edits them. Nothing is disabled or re-generated.
  • There is no link to call. A template has no inputs, no with: and no version reference — you cannot pass values to it from a job.

That independence is why templates suit scaffolding (a recommended CI layout teams adapt) and are the wrong tool when every repository must stay on the same, centrally fixed pipeline.

Organization workflow templates: where they live and their metadata

An organization publishes its own workflow templates by committing them to the workflow-templates directory of the organization's .github repository. Each template needs two files with the same base name:

  • workflow-templates/<name>.yml — the workflow itself.
  • workflow-templates/<name>.properties.json — its metadata. Without this file the template is not offered under New workflow, even when the YAML is valid.
{
  "name": "Octo Org Node.js CI",
  "description": "Build and test a Node.js project.",
  "iconName": "octicon package",
  "categories": ["JavaScript", "Node"],
  "filePatterns": ["package.json$"]
}
KeyRequired?What it does
nameYesTitle shown in the template list
descriptionYesShort description shown with it
iconNameNoAn SVG file in workflow-templates or an Octicon (octicon <name>)
categoriesNoLanguages or categories used to group and filter templates
filePatternsNoRegular expressions matched against files in the repository's root directory; the template is suggested when one matches

Do not confuse filePatterns with the paths filter in a workflow's trigger: filePatterns decides where a template is suggested, while paths decides when a created workflow runs.

A template's YAML can use the $default-branch placeholder, for example branches: [ $default-branch ]. GitHub replaces it with the repository's default branch name once, when a workflow is created from the template, so one template works for repositories that use main, trunk or develop. After that:

  • The copy holds the literal name. The committed file says branches: [ main ], not the placeholder.
  • It does not follow a rename. If the repository's default branch is later renamed, the created workflow still names the old branch until someone edits it, because renaming a branch does not rewrite the files that reference it.
  • It only works in templates. Typed into an ordinary workflow file in .github/workflows, $default-branch is not substituted; it is just text. It is not an expression either, and ${{ }} expressions are not allowed in branch filters.

Creating and governing templates is an administration task covered with the enterprise governance topic; a consumer needs to recognise where they live and why one does or doesn't appear.

Non-public organization templates: visibility and access

Which repositories can be offered an organization's templates depends on the visibility of the .github repository that holds them:

.github repository is…Templates are offered to…
PublicAll repositories in the organization (public, internal and private)
InternalInternal and private repositories
PrivatePrivate repositories only

Read the table as a ceiling, not a default: a private .github repository keeps its templates away from internal repositories as well as public ones, and an internal one never serves public repositories. To keep templates confidential to private repositories, the .github repository must itself be private.

For an internal or private .github repository, the person creating the workflow also needs Read access to it. Granting Read access to the people or teams who need the templates is the way to share non-public templates while the .github repository itself stays restricted; changing its visibility widens who can read it. The repository's Actions access setting (below) is a different control: it lets workflows call reusable workflows and actions, and has nothing to do with which templates a person sees.

Templates are organized per organization: each organization publishes its own from its own .github repository.

Calling a reusable workflow: uses syntax and refs

You consume a reusable workflow with uses on a job — never on a step. A step's uses runs an action; a job's uses calls a workflow file.

jobs:
  build:
    uses: acme/platform-workflows/.github/workflows/lint.yml@v3
  unit:
    uses: $/.github/workflows/unit.yml
  • Another repository: {owner}/{repo}/.github/workflows/{filename}@{ref}. The full path to the file and the @ref are both required; owner/repo/build.yml@v2 or owner/repo@v2/... are invalid.
  • The same repository: $/.github/workflows/{filename} (the form GitHub now recommends) or ./.github/workflows/{filename}, both with no @ref. The called file comes from the same commit as the caller, so a pull request branch tests its own version of both files. Adding a ref to these forms is not allowed. The $/ form is not available on GitHub Enterprise Server, where ./ remains the way to write it.
  • No expressions. uses is read before the run starts, so contexts and expressions such as ${{ vars.CI_TAG }} or ${{ github.sha }} cannot be used in it.

The ref decides how stable the call is:

RefBehaviour
Full commit SHAImmutable — always the reviewed code until the caller is changed. The safest choice for stability and security.
Release tag (v1.4.0)Readable, but a tag can be moved or re-created by the owner.
Major tag (v1) or branchMoves by design, so callers pick up new releases automatically.

If a tag and a branch share a name, the tag takes precedence when the ref is resolved, so keep tag and branch names distinct in repositories that publish reusable workflows. Prefixes such as refs/heads/ are not used in the ref.

Defining on: workflow_call with inputs, secrets and outputs is covered in the workflow triggers lesson; this lesson covers consuming the workflow.

What a calling job can contain

A job that calls a reusable workflow is not a normal job: the called workflow's own jobs choose runners and run steps, so the calling job accepts only a short list of keywords:

  • name, uses, with, secrets (including secrets: inherit)
  • strategy (matrix), needs, if, concurrency, permissions

Anything else — runs-on, steps, env, environment, timeout-minutes, container, services — makes the workflow invalid. Fix it by removing the key and, if the value matters, passing it to the called workflow as an input.

Fanning out with a matrix is allowed: put strategy.matrix on the calling job and pass the value through with:. Each matrix combination calls the reusable workflow once, in parallel.

jobs:
  rollout:
    strategy:
      matrix:
        cluster: [blue, green, canary]
    uses: acme/release-pipelines/.github/workflows/rollout.yml@9c41e7a2d58b3f06e1a7c2d94b8f5e03a6d17c2b
    with:
      cluster: ${{ matrix.cluster }}

Environments belong on the called workflow's jobs. Because environment is not allowed on the calling job, approvals and environment secrets come from the deploy job inside the reusable workflow. To keep one workflow for staging and production, declare an input (for example target) and set environment: ${{ inputs.target }} on the called job; the production environment's protection rules and secrets then apply when the caller passes production.

Access, nesting and runtime context of reusable workflows

A caller can use a reusable workflow only if it can access it. Workflows in a public repository can be called from anywhere. For a private (or internal) repository, an administrator of the repository that holds the workflows must open Settings > Actions > General > Access and allow access from repositories in the organization (or enterprise). This repository-level setting is the only switch: access is not granted by tokens or by permissions on the calling workflow.

  • A private repository can share only with private repositories; an internal repository with internal and private ones. A public repository can never call a workflow in a private repository — move the workflows it needs into a public repository instead.
  • Read access for people is not the same thing: granting a team Read access does not let workflows call the repository's workflows.

Nesting rules. Reusable workflows can call other reusable workflows:

  • A chain can have at most ten levels: the top-level caller plus up to nine nested reusable workflows.
  • Loops are not permitted, whatever conditions the jobs have.
  • Every workflow in the chain must be accessible to the caller. If a nested workflow sits in another private repository still on default settings, the run fails until that repository's Actions access setting allows the organization.
  • A single workflow file can call at most 50 unique reusable workflows, counting nested trees.

Permissions only go down. The GITHUB_TOKEN permissions of a called workflow can be kept or reduced through the chain, never elevated. If a called job asks for more than the caller granted (for example contents: write when the caller allows only contents: read), the extra permission is not granted and the run fails with a permissions error instead of running the job. Grant the permission on the caller (or its calling job) instead.

Context and variables. The github context in a called workflow always describes the caller's run: github.repository is the calling repository and github.sha the commit that triggered it, not the repository or ref of the reusable workflow. Variables in the caller's workflow-level env are not propagated to the called workflow, and the called workflow's env is not visible to the caller — pass values as inputs.

Consuming a composite action

A composite action is consumed as a step: uses: acme/toolchain-setup@v1 inside an existing job's steps. Its steps run in place, which gives it three defining properties:

  • Same runner and workspace. It runs on the calling job's runner and sees the files earlier steps wrote — checked-out code, compiled binaries — with no artifact upload. action.yml has no runs-on; it declares runs: using: composite.
  • Data in through inputs. Values come from with: on the step and must be declared as inputs in action.yml. The secrets context is not available inside the action's own files, so referencing a secret there fails as an unrecognized context. Declare an input and have the calling workflow pass the secret into it with with:; the value stays masked in logs. secrets: inherit is only for calling jobs.
  • One step in the log. It appears as a single step of the calling job, not as a separate job in the run graph.
steps:
  - uses: actions/checkout@v5
  - uses: acme/cache-warmup@v1
    with:
      api-key: ${{ secrets.CACHE_API_KEY }}
  - run: make integration-test

Composite actions suit repeated setup steps and post-build steps that must touch the job's files. They cannot run work on a different runner or show each shared step separately in real time — that needs a reusable workflow. Building and publishing actions is covered in the custom actions lessons.

Disabling versus deleting a workflow

Disabling a workflow pauses it everywhere while keeping its file and history; deleting the workflow file removes the definition from the branch where it is deleted, and only from that branch.

Disable the workflowDelete the workflow file
Workflow fileUnchanged in every branchRemoved from that branch by a commit
Which runs stopAll triggers on all branchesOnly events evaluated against branches without the file; other branches keep their copies and keep running them
Commit neededNoYes
Coming backEnable it again — no code changeRestore the file in a new commit
Run historyKeptPast runs are not removed by deleting the file

Ways to disable or enable a single workflow:

  • UI: Actions tab > select the workflow > the … menu > Disable workflow (later, Enable workflow).
  • GitHub CLI: gh workflow disable nightly-report.yml and gh workflow enable nightly-report.yml.
  • REST API: PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable and …/enable. workflow_id accepts the numeric ID or the file name, which makes it easy to script across many repositories.

Don't confuse this with turning Actions off for the whole repository (PUT /repos/{owner}/{repo}/actions/permissions with enabled: false, or the repository settings page), which stops every workflow, or with cancelling a run, which stops only that run. Editing the file is no substitute either: an event runs the workflow file as it exists in the commit or branch the event is evaluated against, so a trigger change committed to one branch does not change the copies other branches still carry. Disabling is the one action that stops a workflow on every branch without changing any branch.

A workflow's state is reported by the API as active, disabled_manually, disabled_inactivity, disabled_fork or deleted.

When GitHub disables workflows automatically

GitHub disables some workflows on its own, and the fix is to enable them again — new activity does not do it.

  • Inactive public repositories. In a public repository, scheduled workflows are automatically disabled when there has been no repository activity for 60 days. A maintainer re-enables the workflow from the Actions tab, with gh workflow enable or the REST API. Later activity in the repository does not switch it back on by itself, and scheduled workflows always run from the default branch.
  • Forks. When you fork a public repository, its scheduled workflows are disabled by default in the fork, so a nightly job upstream does not start running on the fork's account. Enable them in the fork if you want them.

These states show up in the API as disabled_inactivity and disabled_fork, distinct from a workflow someone disabled manually.

Tip. Expect scenario items that state a requirement and ask which sharing mechanism fits: a central fix every repository picks up without edits, shared steps that must run inside an existing job's workspace, or a shared job on a different runner with per-step logs. Template items test that a created workflow is an independent copy, where organization templates and their .properties.json metadata live, filePatterns versus trigger filters, when $default-branch is substituted and what a created copy holds after a branch rename, and which repositories see templates from a public, internal or private .github repository. Reusable workflow items show a short YAML calling job and ask which uses value or ref is valid, which keys must be removed, how to fan out with a matrix, where an environment belongs, and why a call to a private or nested workflow fails. Disable-versus-delete items add constraints such as keeping the file, changing no branch or scripting many repositories.

Key takeaways
  • Template = a one-time copy; reusable workflow = central jobs called by a job; composite action = central steps run inside a job.
  • A workflow created from a template is independent: customise it in its own repository, and later template edits never reach it.
  • Organization templates live in workflow-templates/ of the .github repository as <name>.yml plus <name>.properties.json (name and description required).
  • filePatterns decides where a template is suggested; $default-branch is replaced with the default branch's name once, at creation, so the copy keeps that literal name after a rename and the placeholder means nothing in an ordinary workflow file.
  • A public .github repository serves all repositories, internal serves internal and private, private serves private only; people also need Read access to a non-public one.
  • Call reusable workflows with jobs.<id>.uses: owner/repo/.github/workflows/file@ref, or $/.github/workflows/file (recommended) or ./.github/workflows/file for the caller's own commit; no expressions in uses.
  • Pin to a full commit SHA for immutability; a tag beats a branch of the same name.
  • Calling jobs allow only name, uses, with, secrets, strategy, needs, if, concurrency and permissions; environments go on the called job.
  • Private reusable workflows need the holding repository's Actions access setting; chains allow ten levels, no loops, and permissions that only go down.
  • Disabling stops a workflow on every branch and keeps the file; deleting the file only affects the branch that drops it; re-enable auto-disabled scheduled workflows explicitly.

Frequently asked questions

What is the difference between a workflow template and a reusable workflow in GitHub Actions?

A workflow template is copied into a repository when someone creates a workflow from it, and the copy is then independent, so later template changes never reach it. A reusable workflow stays in one place and is called from a job with jobs.<job_id>.uses, so every caller runs the current code of the ref it names. Use a template for a starting point teams adapt, and a reusable workflow when a central fix must reach every caller without editing them.

When should I use a composite action instead of a reusable workflow?

Use a composite action when shared logic must run as steps inside an existing job, on that job's runner, using files earlier steps created, without uploading artifacts. Use a reusable workflow when the shared work needs its own jobs or runner, its steps must be logged separately in real time, or it must handle secrets with the secrets keyword. Composite actions can also be published to GitHub Marketplace; reusable workflows cannot.

Why is my organization workflow template not showing under New workflow?

Check that the template is in the workflow-templates directory of the organization's .github repository and has a matching <name>.properties.json file with at least name and description. Then check visibility: a private .github repository only serves private repositories and an internal one serves internal and private repositories, and the person creating the workflow needs Read access to a non-public .github repository. A filePatterns entry also limits suggestions to repositories whose root files match it.

How do you reference a reusable workflow in another repository?

Add uses at the job level with the form owner/repo/.github/workflows/filename.yml@ref, where ref is a commit SHA, tag or branch. For a workflow in the same repository use $/.github/workflows/filename.yml (the recommended form, not available on GitHub Enterprise Server) or ./.github/workflows/filename.yml, both without a ref, which take the file from the caller's commit. Expressions are not allowed in uses, and a full commit SHA is the most stable and secure ref.

Why does setting environment or runs-on on a job that calls a reusable workflow fail?

A calling job only supports name, uses, with, secrets, strategy, needs, if, concurrency and permissions, because the called workflow's own jobs choose runners and environments. Remove the unsupported key and set runs-on or environment on the jobs inside the reusable workflow, passing the value as an input (for example environment: ${{ inputs.target }}) if callers need to choose it.

How do I let other repositories use reusable workflows stored in a private repository?

In the private repository that holds the workflows, go to Settings > Actions > General and under Access allow repositories in the organization (or enterprise) to use them. Only private repositories can then call them; public repositories never can. In a nested chain, every private repository involved needs this setting, because every workflow in the chain must be accessible to the caller.

What is the difference between disabling and deleting a GitHub Actions workflow?

Disabling a workflow, from the Actions tab, with gh workflow disable or with the REST disable endpoint, stops it on every branch without changing the file, and enabling it brings it back with no commit. Deleting the workflow file is a commit that only affects the branch it lands on, so other branches that still contain the file keep running it, and restoring it needs another commit.

Why did my scheduled GitHub Actions workflow stop running?

In a public repository, GitHub disables scheduled workflows after 60 days without repository activity, and in a fork of a public repository scheduled workflows are disabled by default. In both cases re-enable the workflow from the Actions tab, with gh workflow enable or through the REST API; a new push alone does not re-enable it.

Source

This lesson covers the "Consume and troubleshoot workflows" domain of the official GH-200 exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?