Trigger a workflow from another workflow with GitHub Actions
Learn how to trigger a workflow from another workflow, even from another repository, using a GitHub App token and not personal access tokens.
When you dispatch an event (such as workflow_dispatch) using the default ${{ secrets.GITHUB_TOKEN }}, GitHub deliberately does not start a new workflow run. This is a built-in safeguard to prevent runaway recursive runs. To actually trigger another workflow you must authenticate with a different identity — a GitHub App token (recommended) or a personal access token (PAT).
This guide uses a GitHub App token, which is scoped to specific repositories, short-lived, and not tied to an individual user account. The same limitation applies even within a single repository, so the GitHub App approach below works for both same-repository and cross-repository triggers.
Step 1: Create the GitHub App#
- Go to your GitHub account settings and create a new GitHub App.
- Generate a private key for the app and note down the
App ID. - Install the GitHub App on the repository where you want to trigger the workflow.
Step 2: Store the credentials#
- Add the App ID and the private key as secrets in your repository settings. Name them
APP_IDandAPP_PRIVATE_KEYrespectively (these are the names referenced in the workflow below).
Step 3: Create the triggered workflow#
Create a workflow file (e.g., .github/workflows/triggered-workflow.yml) that will be triggered by another workflow.
name: Triggered Workflow
on: workflow_dispatch:
jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6
- name: Run a script run: echo "This is the triggered workflow"Step 4: Create the triggering workflow#
Create another workflow file (e.g., .github/workflows/triggering-workflow.yml) that will trigger the above workflow.
name: Triggering Workflow
on: push: branches: - main
jobs: trigger: runs-on: ubuntu-latest steps: - name: Generate GitHub App token id: generate_token uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} # By default the token is scoped to the current repository only. # When dispatching to a different repository, scope it explicitly: owner: my-org repositories: triggered-repo
- name: Trigger another workflow uses: benc-uk/workflow-dispatch@v1 with: workflow: triggered-workflow.yml token: ${{ steps.generate_token.outputs.token }} repo: my-org/triggered-repo ref: mainExplanation#
-
Triggered Workflow:
- This workflow (
triggered-workflow.yml) is set to run on theworkflow_dispatchevent, which allows it to be triggered manually or by another workflow.
- This workflow (
-
Triggering Workflow:
- This workflow (
triggering-workflow.yml) runs on a push to themainbranch. - It uses the
actions/create-github-app-token↗ action to generate a GitHub App token using the App ID and private key stored in theAPP_IDandAPP_PRIVATE_KEYrepository secrets. - The
ownerandrepositoriesinputs scope the token to the target repository. By default the token only has access to the repository running the workflow, so these are required when dispatching to a different repository. For a same-repository trigger you can omit them. - It uses the
benc-uk/workflow-dispatch↗ action to trigger thetriggered-workflow.ymlby dispatching a workflow dispatch event. - The
workflowinput specifies the workflow file to be triggered. - The
tokeninput uses the generated GitHub App token. - The
repoinput specifies the repository where the event should be dispatched. - The
refinput specifies the branch or tag to use for the triggered workflow run.
- This workflow (
Note#
- The GitHub App needs the Actions: Read and write repository permission to dispatch workflow events, and it must be installed on the target repository.
- For a cross-repository setup, the App must be installed on the repository that owns the triggered workflow (
my-org/triggered-repoabove), since that is where the dispatch event is delivered. - When the App token is generated, it is scoped to the repositories where the App is installed and expires after a short time (about one hour), which is what makes it safer than a long-lived personal access token.