actions/add-to-project: putting issues and pull requests on a board automatically
Automate adding issues and pull requests to GitHub projects
At a glance
- What is it?
- This GitHub Action adds the issue or pull request that triggered a workflow to a GitHub project, optionally filtered by label with AND, OR or NOT semantics. It only works with the new Projects experience, it needs a personal access token with project scope, and the action ships a compiled dist directory that is what actually runs.
- Who is it for?
- Adopt actions/add-to-project if you triage incoming issues through a GitHub project board and want the board populated by the workflow that already runs on those issues. Keep away if you are on classic project boards, because the action explicitly does not support them, and keep away if you would rather avoid giving a workflow a personal access token that can read and write projects.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A board that fills itself from the events you already handle
actions/add-to-project is a GitHub Action that automatically adds the current issue or pull request to a GitHub project. It works with the new Projects experience, and the README notes explicitly that it does not support GitHub projects classic, with a link to the classic project boards documentation.
That limitation is worth understanding before you plan the workflow, because classic boards and the new Projects experience have different data models and different APIs, and this action only speaks to the new one.
The use case is a triage board. You already have a workflow that runs when issues or pull requests are opened or labeled, and this action adds the triggering item to the project as part of that run. The README notes that the workflow also supports adding issues to your project which are transferred into your repository, which covers the case of an issue created elsewhere and moved in.
The filtering is the part that makes it more than a catch-all. You can configure filters such as only adding issues with certain labels, and you may match labels with an AND or an OR operator, or exclude labels with a NOT operator. So a board can be for everything labeled needs-triage, and a second workflow can be for everything that is not a bug, and both can run on the same event.
The action is MIT licensed, the primary language is TypeScript, the default branch is main, the last push was on 2026-10-19, and the release history shows 1.0.1 in April 2024, 1.0.2 in June 2024 and 2.0.0 on 2026-05-04, so the 2.x line is a major version bump on a project that had been stable for a while.
A workflow is three inputs and a label operator
The metadata for the inputs, outputs and run configuration lives in the action.yml file, and the README points there rather than restating it.
The example for an issue opened with either of two labels is the shortest complete workflow in the documentation:
name: Add bugs to bugs project
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@RELEASE_VERSION
with:
# You can target a project in a different organization
# to the issue
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: bug, needs-triage
label-operator: ORThree things in there are the whole API. The project URL points at the board, and the comment notes you can target a project in a different organization from the issue. The token is a repository secret, and the reason it is a secret rather than a literal is that it is a personal access token. And the label list plus the operator define the filter.
The exclusion variant uses the same shape with a NOT operator, which produces the complement set:
name: Adds all issues that don't include the 'bug' or 'needs-triage' labels to project board
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@RELEASE_VERSION
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: bug, needs-triage
label-operator: NOTAnd the pull request variant changes the event trigger rather than the inputs:
name: Add needs-review and size/XL pull requests to projects
on:
pull_request:
types:
- labeled
jobs:
add-to-project:
name: Add pull request to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@RELEASE_VERSION
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: needs-review, size/XL
label-operator: ANDThe trigger is the interesting part of that last one. Running on the labeled event rather than on opened means the board is populated when a reviewer asks for a re-review, not when the pull request arrives, which is a different and usually better workflow.
The four inputs, and what the operators actually mean
The inputs section is precise about the semantics, and the label behaviour is the part people get wrong.
project-url is required and is the URL of the GitHub project to add issues to, with the form given as either the orgs or the users variant followed by the project number. github-token is required and is a personal access token with repo and project scopes. The token input is what the action passes through to the API calls.
labeled is optional and is a comma-separated list of labels used to filter applicable issues. The rule is that when this key is provided, an issue must have one of the labels in the list to be added to the project, and omitting the key means that any issue will be added. That is a useful default, because the unfiltered case needs no configuration at all.
label-operator is optional and is the behaviour of the labels filter, either AND, OR or NOT, and it controls whether the issue should be matched with all of the labeled input or with any of them. The default is OR.
The four supported events are listed for issues and pull requests separately. The issues events are opened, reopened, transferred and labeled, and the pull request events are opened, reopened and labeled. The reason the two lists differ is that a pull request cannot be transferred, and the action only supports what the underlying API allows.
The README also says that using these events ensures a given issue or pull request in the workflow's repo is added to the specified project, and that if labeled inputs are defined then issues are only added if they contain at least one of the labels in the list. The distinction between the input documentation and the supported events section is worth noticing, because the first describes the filter and the second describes when the action runs.
The token, and the two ways to create it
The PAT section is the part to read carefully, because the two token types need different things and the action cannot work without one.
For a classic token you include the project scope, and for private repositories you also need the repo scope. That is the short version for people who already have one.
For a fine-grained token the process is longer and more precise. You first select the appropriate owner and the associated repositories. Then you select organization permissions for projects with read and write, and repository permissions for issues with read-only and for pull requests with read-only.
The asymmetry is the point. A fine-grained token gets write access to projects and read-only access to issues and pull requests, which is exactly the permission set the action needs, and it is scoped to specific repositories. A classic token with repo and project scopes is broader, and the repo scope in particular grants access to repository contents rather than just to issue metadata.
Whichever you choose, you add it as a repository secret, and that secret is referenced by the github-token input. The examples in the README all use the same secret name, ADD_TO_PROJECT_PAT.
There is also a section on setting a specific status or column name to the project item, which the table of contents lists. The default behaviour is presumably to let the project decide, and this input is for the case where the board has a status column you want set to something particular.
There is a further consideration the README does not spell out but which follows from the design. A personal access token belongs to a person, not to the workflow, so the board belongs to whoever created the token unless the token belongs to a bot account. That is a decision to make deliberately, because an organisation's project board and a departing employee's token do not mix well.
The dist directory, and how this action is built and released
The repository layout explains how the action is distributed, and it follows the standard pattern for a JavaScript GitHub Action.
There is an action.yml at the top level, which is the metadata GitHub reads, and a dist/ directory holding the compiled output. The src/ directory holds the TypeScript source, and a build script compiles it into dist. That matters because GitHub Actions does not run npm install for a JavaScript action, it runs the committed dist directory, so the compiled bundle in the repository is what actually executes on a runner. It also means a pull request that changes src without rebuilding dist changes nothing until the build is run, which is why the build is wired into the pull request checks.
The build tooling is in the manifest. The bundler is vercel's ncc, the test runner is jest with ts-jest for the TypeScript tests, and there is a __tests__ directory. The linter is eslint with the typescript-eslint plugin and the eslint-plugin-github plugin, and the formatter is prettier with the shared @github/prettier-config. There is a fix-regex.js file at the top level, which suggests a small script that rewrites something in the source during the build, and a jest.config.js.
Releasing is automated with semantic-release. The devDependencies include the semantic-release commit analyzer, the GitHub release publisher, the release notes generator and the release package itself, and the repository has a .releaserc.json and a RELEASING.md. So a version bump is a consequence of a conventional commit message rather than a manual edit, and that is how the 2.0.0 major version arrived without anyone editing a version field by hand.
The package itself is marked private, its version field in the manifest is 0.0.0, and the published name is @actions/add-to-project, which is why the manifest version does not match the release tags.
Where add-to-project is the wrong choice
The first wrong case is a team that does not want a personal access token in its workflow. The action needs a token with project write access, and the token belongs to a person or a bot account, which means a credential that outlives the workflow and has to be rotated when someone leaves. A GitHub App with narrower permissions is the tidier answer, and this action is not that.
The second is a project that lives on classic project boards. That is a hard exclusion in the README, and there is no workaround in this action, because the underlying API is the new Projects one.
The third is a board that is meant to be curated rather than filled. The action adds every item matching the filter, and a board where a human decides what belongs is a different artefact. The filters help, but they express a rule, not a judgement.
The fourth is a repository that already automates this. A board fed by a workflow that also fires on label changes can double-add items, and while adding an item to a project twice is usually harmless, the workflow will run on every labeled event, so a pull request that gets labeled needs-review and then size/XL will be added twice unless you scope the trigger carefully.
The alternative worth weighing is doing it by hand or with a different trigger. The new Projects interface has its own built-in automation rules that can add items based on labels and fields, and if your needs are a simple label-to-board mapping, that is a config screen rather than a workflow with a token. The action earns its place when you need the flexibility of AND, OR and NOT filters combined with a specific event, or when you need the board fed from a transferred issue.
Editorial conclusion
Adopt actions/add-to-project if you triage incoming issues through a GitHub project board and want the board populated by the workflow that already runs on those issues. Keep away if you are on classic project boards, because the action explicitly does not support them, and keep away if you would rather avoid giving a workflow a personal access token that can read and write projects. Before you wire it up, read the token section, since a classic token and a fine-grained token need different scopes, and note the release line is on v2 with 2.0.0 from 2026-05-04 and the last push on 2026-10-19.
Frequently asked questions
What does actions/add-to-project do and which project type does it support?
It is a GitHub Action that automatically adds the current issue or pull request to a GitHub project, on the opened, reopened, transferred or labeled events for issues and opened, reopened or labeled for pull requests. It supports the new Projects experience and explicitly does not support GitHub projects classic.
How do I filter which issues get added to the project?
Use the optional labeled input with a comma-separated list, and the label-operator input set to AND, OR or NOT, with OR as the default. When labeled is provided an issue must have one of the listed labels, and omitting it means any issue is added.
What permissions does the GitHub token need for add-to-project?
For a classic personal access token you need the project scope, and repo as well for private repositories. For a fine-grained token you select the owner and repositories, then organization permissions for projects with read and write, and repository permissions for issues and for pull requests as read-only. The token is added as a repository secret.
How is the add-to-project action built and released?
The TypeScript source in src/ is compiled into the committed dist/ directory with ncc, and GitHub runs that committed bundle rather than installing dependencies. Releases are automated with semantic-release, so a version bump follows from a conventional commit, and the manifest version field stays at 0.0.0 because the published package is marked private.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/actions-add-to-project)Was this useful?