# repository-dispatch: a GitHub action for firing a custom event at another repository

> repository-dispatch wraps the repository_dispatch endpoint in a small TypeScript action, so one workflow can tell another repository to start a run with a custom event name and a JSON payload. The interesting constraints are all in the GitHub API underneath it: the ten property cap on a client payload, the requirement that the receiving workflow sit on the default branch, and the need for a personal access token whenever the target is not the current repository.

**peter-evans/repository-dispatch** — A GitHub action to create a repository dispatch event

- Repository: https://github.com/peter-evans/repository-dispatch
- Stars: 1,217 · Forks: 156
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/peter-evans-repository-dispatch

## What the action actually does

repository-dispatch is a GitHub action to create a repository dispatch event, and the repository description says the same in one line. The action creates repository_dispatch events, which is a webhook-style event with a name you choose, and the receiving workflow subscribes to that name.

The caller side is a job in a workflow file:

```yaml
jobs:
  Dispatch:
    runs-on: ubuntu-latest
    permissions:
      contents: write
     steps:
       - name: Repository Dispatch
         uses: peter-evans/repository-dispatch@v4
         with:
           event-type: my-event
```

And the callee side is a workflow triggered by that event type:

```yaml
name: MyWorkflow

on:
  repository_dispatch:
    types: [my-event]
```

That is the whole contract. The action exists because the underlying API call needs a token with the right scope, and because doing that by hand inside a workflow means writing a curl invocation, handling the response and failing the job correctly. The action does the call, and the README is a reference for its four inputs rather than a tutorial.

The four inputs are token, which defaults to GITHUB_TOKEN, repository, which defaults to github.repository and so means the current repository, event-type, which is the only required one, and client-payload, a JSON payload with extra information that the action or the workflow may use, defaulting to an empty object.

The action is MIT licensed, is listed in the GitHub Marketplace, and the current release line is v4, with v4.0.1 published on 2025-11-14 and the last push to the repository on 2026-10-12.

## Same repository or another one decides your token

The token section is the part that catches people, and the rule is short. The default GITHUB_TOKEN token can only be used if you are dispatching the same repository that the workflow is executing in. To dispatch to a remote repository you must create a Personal Access Token with the repo scope and store it as a secret.

The remote case therefore needs a credential that lives longer than the job, and that is a design decision rather than a limitation of the action. The GITHUB_TOKEN is minted per run and scoped to the repository, so it cannot reach across repositories at all. A personal access token can, and that is exactly why it is also a credential worth protecting.

The README gives three variants, and the differences are worth reading. A classic personal access token needs the repo scope, and for a public repository you can use the more limited public_repo scope. A fine-grained personal access token, which the README marks as beta, needs specific permissions on the target repositories: contents read and write, and metadata read only, which is automatically selected when you choose the contents permission.

So the practical matrix is small. Same repository, default token, contents write permission on the job, and nothing to manage. Cross repository, a secret token with write access to the target, and a decision about how that secret is scoped and rotated.

The action itself does not cache or refresh anything, and it does not try to be clever about which token to use. It uses the one you give it, which means the security question lives entirely in how you choose it.

## The receiving workflow has to be on the default branch

There is a constraint in the README's example section that is easy to miss and expensive to debug, because the symptom is a workflow that never runs.

Note that repository dispatch events will only trigger a workflow run if the workflow is committed to the default branch.

That is a property of how the event is delivered rather than of the action, and it changes how you develop against it. A workflow file sitting on a feature branch will not be triggered by a dispatch, which means the usual workflow of pushing a change to a branch and testing it does not work here. The event arrives, the repository looks for a handler on the default branch, and finds none.

The example receiving workflow shows the rest of the shape, including how the payload is read:

```yaml
name: Repository Dispatch
on:
  repository_dispatch:
    types: [my-event]
jobs:
  myEvent:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.client_payload.ref }}
      - run: echo ${{ github.event.client_payload.sha }}
```

The payload arrives as client_payload on the event, and the example uses it to check out a specific ref and echo a sha. That is the intended shape of the whole mechanism. One repository hands another a reference and a commit, and the receiving workflow acts on it.

The alternative to this approach is polling. A target repository that watches a queue, or a workflow that runs on a schedule and checks for work, needs no shared credential, no default branch constraint and no custom event, at the cost of latency and of a mechanism that has to notice new work on its own. The push model here is instant and carries data, and it is what the token and the branch rules are the price of.

## The client payload is capped at ten top-level properties

The client payload has a limit that is enforced by the API and is documented clearly, which is rarer than it should be.

The GitHub API allows a maximum of 10 top-level properties in the client-payload JSON. If you use more than that you will see an error message like this:

```
No more than 10 properties are allowed; 14 were supplied.
```

The example that fails is the obvious one, passing the whole GitHub context:

```yaml
client-payload: ${{ toJson(github) }}
```

The github context has more than ten properties, so that payload is rejected. The fix is to wrap it in a single top-level property, and then it succeeds:

```yaml
client-payload: '{"github": ${{ toJson(github) }}}'
```

There is also a limitation on the total data size of the client-payload, and a very large payload may result in a client_payload is too large error. That is the one limit the README describes without giving a number, so the size ceiling has to be found by trying it.

For a multiline payload you can set the value directly in YAML, which is where the flexibility shows. The example builds a nested object with a repo section carrying the repository name, a branch and a tag from a previous job's outputs, and a deployment section with a project, a container and a Slack message taken from environment variables.

The design lesson is that the payload is a small envelope, not a data transport. Ten top-level keys is enough for a reference and a couple of parameters, and anything more wants to go in the repository or in a store the workflow can reach.

## Dispatching to many repositories is a matrix job

The last documented pattern is the one that makes this action useful in a monorepo or a fleet of service repositories, and it uses the matrix strategy rather than a loop.

You can dispatch to multiple repositories by using a matrix strategy. In the example, after the build job succeeds, an event is dispatched to three different repositories:

```yaml
jobs:
  build:
    # Main workflow job that builds, tests, etc.

  dispatch:
    needs: build
    strategy:
      matrix:
        repo: ['my-org/repo1', 'my-org/repo2', 'my-org/repo3']
    runs-on: ubuntu-latest
    steps:
      - name: Repository Dispatch
        uses: peter-evans/repository-dispatch@v4
        with:
          token: ${{ secrets.PAT }}
          repository: ${{ matrix.repo }}
          event-type: my-event
```

The needs: build dependency is what makes this a fan-out rather than a race. The build completes first, and only then does the dispatch job start, once per repository in the matrix, each with the same event type and the same personal access token from the same secret.

The approach has a cost worth naming. One secret, with write access, is used to dispatch to every repository in the list, so the blast radius of that secret is the union of all those repositories. A design that gave each repository its own token would narrow that, at the price of a matrix over secrets as well as repositories. The README does not discuss the trade, and it is the decision you would be making by adopting this pattern.

## A small action with a bundled dist and a Node floor

The repository is small in the way an action should be, and the build files describe the contract with GitHub Actions.

There is an action.yml at the root describing the action, a src directory with the TypeScript source, and a dist directory, which is the bundled JavaScript that GitHub actually runs. That arrangement is the convention for actions written in a compiled language, and it is why the dist directory is committed rather than built on the runner.

The package metadata shows a narrow dependency list at runtime, with @actions/core and @actions/github, which are the toolkit for reading inputs, writing outputs and calling the API. The development dependencies are the toolchain, and they show the era of the project: typescript-eslint 5.x, eslint 8.57.1, typescript 5.4.5, and jest 30 with ts-jest, plus @vercel/ncc for the bundler and prettier 3.9.9 for formatting. The build script is tsc followed by ncc build, the lint script is eslint over the source, and the test script is jest with --passWithNoTests, which is a flag that admits the test suite may be empty.

One constraint to note is the runtime floor, with engines requiring node >= 24.4.0. That is the version of Node the action itself runs on inside the runner, and it is a much higher floor than the action's own dependencies suggest, so a self-hosted runner on an older Node will not be able to run it.

The release history is short and the majors are infrequent: v3.0.0 in January 2024, v4.0.0 in October 2025, and v4.0.1 in November 2025, with work continuing through to October 2026. For a component that sits in the middle of other people's pipelines, that is a sensible rate of change, and pinning to a major version is the right habit here.

## Conclusion

repository-dispatch fits a setup where a build in one repository should hand work to a deployment workflow in another, with data attached, and where you would rather push than poll. It does not fit a same-repository case that a plain trigger or a scheduled workflow already handles, and it does not fit an organisation that does not want long-lived personal access tokens stored as secrets. Before you use it across repositories, decide which token each workflow gets, and keep the client payload under ten top-level properties, because that limit is enforced by the API and the error message does not appear until a dispatch has already failed.

## FAQ

### What does the repository-dispatch action do?

It creates a repository_dispatch event in a GitHub repository, with an event type you choose and an optional JSON client payload. A workflow in the target repository triggers on that event type through on: repository_dispatch.

### Which token does repository-dispatch need to dispatch to another repository?

The default GITHUB_TOKEN can only be used when dispatching to the same repository the workflow is executing in. For a remote repository you need a personal access token stored as a secret, with the repo scope, or the more limited public_repo scope for a public repository, and a fine-grained token needs contents read and write plus metadata read only.

### Why does my repository_dispatch workflow not trigger?

Repository dispatch events will only trigger a workflow run if the workflow is committed to the default branch. A workflow file that only exists on a feature branch will not be triggered by a dispatched event.

### How many properties can a repository-dispatch client payload have?

The GitHub API allows a maximum of 10 top-level properties in the client-payload JSON, and more than that produces an error saying no more than 10 properties are allowed. There is also a total data size limit, and a very large payload may fail with a client_payload is too large error.

### Can repository-dispatch send an event to several repositories at once?

Yes, by using a matrix strategy, with a job that needs the build job and a matrix listing the repositories, and the repository input set to the matrix value. The README's example dispatches to three repositories with the same event type and the same personal access token secret.

## Sources

- [Issues](https://github.com/peter-evans/repository-dispatch/issues)
- [License: MIT](https://github.com/peter-evans/repository-dispatch/blob/main/LICENSE)
- [peter-evans/repository-dispatch on GitHub](https://github.com/peter-evans/repository-dispatch)
- [README](https://github.com/peter-evans/repository-dispatch/blob/main/README.md)
- [Releases](https://github.com/peter-evans/repository-dispatch/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/peter-evans-repository-dispatch
