# Changelog
Source: https://docs.prflow.dev/changelog
New features in PRFlow, week by week.
π¬ **Conversation comments on GitHub pull requests now sync into the Slack thread.** Inline review comments already did; both kinds now show up as threaded replies. GitHub delivers conversation comments as issue comments, so the App needs one extra read-only permission, Issues. Existing installations get a one-time prompt for an organization owner to approve it; comments on plain issues are ignored.
π **Draft PRs can now post to Slack, per rule.** Every [routing rule](/channel-routing) has a Draft PRs toggle, off by default: turn it on and the card posts as soon as a draft opens instead of waiting for ready for review.
## π PRFlow is on the GitHub Marketplace
PRFlow for Slack is now published on the
[GitHub Marketplace](https://github.com/marketplace/prflow-for-slack), listed
under Code review and Chat with a free Starter plan. You can install the App
straight from the listing β or from your GitHub organization's Marketplace
browse and search results β and then link it to your PRFlow workspace on the
Connections page. Starting from inside PRFlow still works exactly as before.
π΅π π΄π’π£ **The notification is now a lifecycle card that updates in place.** A colored rail shows state at a glance: blue in review, amber running, red blocked, green ready, purple merged. The same message updates as the MR progresses, so a channel shows one current card per MR instead of a trail of stale posts.
π¬ **GitHub comments now sync into the Slack thread.** Conversation comments and inline review comments on a pull request appear as threaded replies under the PR's Slack message, with edits updated in place and deletions removed. Comment sync now works the same on GitLab and GitHub.
π¬ **Scheduled PR digests in Slack.** At your chosen times, PRFlow posts a digest of a channel's open PRs, classified into Needs attention, Waiting on review, and Ready to merge, with age and staleness highlights. Configure it right where you route repositories: one dialog covers the schedule, timezone, and PR filters for the destination channel.
## πππ GitHub support
Install the PRFlow GitHub App, pick repositories, and map them to Slack channels just like GitLab projects. One updating Slack message per pull request, with CI status from GitHub Actions and other check providers. No webhook setup required.
π‘ **See when GitLab last delivered a webhook.** The Connections page shows "Last event received N hours ago".
π³ **Manage your subscription entirely inside PRFlow.** Billing settings were redesigned: upgrade in an embedded checkout overlay, update your payment method, cancel or resubscribe in one click, and see per-seat prices and seat usage in one place.
β **Redesigned Add Repository dialog.** A searchable project list with multi-select lets you connect several projects to a channel in one step.
π©Ί **GitLab token health at a glance.** Settings shows expiry and health of your GitLab credential.
π§ **Merge blockers shown in Slack.** Notifications tell you why an MR can't merge: missing approvals, unresolved discussions, and other merge-status signals.
π **Self-hosted GitLab shipped.** Cloud and self-hosted instances are now both supported.
π₯ **Teammates and organizations introduced.** Invite teammates and switch organizations from the account menu.
π **Connect GitLab with a service-account token.** Teams on GitLab Premium/Ultimate can register a PAT for webhook management.
π£ **PRFlow is born.** The first release: GitLab merge requests notify your team in Slack, one message per MR that updates in place as the MR moves along. CI status shows on the message, comments sync into a Slack thread, and a guided setup connects GitLab, installs the Slack app, and maps each project to a channel.
# Notification rules
Source: https://docs.prflow.dev/channel-routing
How PRFlow decides which Slack channel a repository posts to: an ordered list of rules where the first match wins, with specific repositories, wildcard patterns, and a catch-all.
PRFlow decides where every notification goes with **notification rules**, managed
from the dashboard at [app.prflow.dev](https://app.prflow.dev). A rule has a
name, a destination Slack channel, one or more match conditions, and an on/off
switch. When an event arrives, PRFlow walks your rules top to bottom and the
first enabled rule with a matching condition wins: the repository's
notifications post to that rule's channel. A repository that matches no rule
posts nowhere.
## Creating a rule
Connect your source provider (GitLab or GitHub) and Slack first, then click
**+ New rule** on the dashboard.
* **Destination channel** β where matched repositories post. Picking a channel
fills in the rule name for you; a name you type yourself is never
overwritten. Each channel belongs to at most one rule, so channels already
claimed show which rule routes them. PRFlow lists public channels plus
private channels it has been invited to β if a private channel is missing,
run `/invite @PRFlow` in that channel on Slack, then reopen the editor.
* **Match conditions** β a **Specific repository** condition is already open
when the editor appears, and if PRFlow only knows about one repository it is
pre-selected too, so picking a channel is the only decision left. Add more
conditions with **+ Add condition**; a repository routes to the rule if it
matches **any** of its conditions.
* **Notifications** β the **Draft PRs** switch, off by default, makes the
rule post drafts too: the card appears when a draft opens instead of when
it is marked ready for review. Rules with it on show a **Drafts** chip in
the rules list.
The same editor holds the channel's [scheduled digest](/pr-digest) settings,
and this is also where you delete a rule.
## The three match types
* **Specific repository** β pick one or more repositories from the list.
Matching is by the repository's exact full name.
* **Repository pattern** β a wildcard expression. `*` is the only wildcard
and matches any run of characters, including `/`. The pattern must match
the whole name; there is no substring matching (`web-app` alone matches
nothing β write `*/web-app`).
* **All repositories** β matches every repository. This is the catch-all you
use for a fallback rule.
### Patterns match different things on GitHub and GitLab
On **GitHub**, a pattern matches the **bare repository name**, without the
owner: write `payments-*` or `*-service`. A GitHub connection covers a single
owner, so the owner prefix carries no routing information β an owner-prefixed
pattern like `your-org/*` matches nothing on GitHub. To route every
repository, use an **All repositories** condition instead.
On **GitLab**, a pattern matches the **full path including group and
subgroups**: `acme/frontend/*` routes every project under the `frontend`
subgroup, and `acme/*/web-app` matches a project named `web-app` in any
subgroup.
## Rule order: first match wins
Rules are checked top to bottom and the first match wins, so order is part of
the configuration. Reorder by dragging a rule up or down by the grip handle on
the left of its row (keyboard: focus the grip, Space to lift, arrow keys to
move, Space to drop, Escape to cancel).
* A **Specific repository** condition has no built-in priority: a pattern rule
higher in the list takes a repository even if a rule below names it
explicitly. To make a specific-repository rule win, drag it above the
pattern.
* Keep the catch-all rule **last** β anything above it can never lose to it.
Nothing enforces this, so the list warns with *"Shadows every rule below"*
when an enabled catch-all sits above other enabled rules.
* A rule switched off is skipped entirely; the rules below it take over.
## New projects register themselves
With a GitLab **group webhook**, GitLab sends events for every project in
your group β including projects created after you set PRFlow up. PRFlow
registers a project from your configured group the first time it sends an
event and applies your rules to it immediately, so a pattern or catch-all
rule picks up new projects with no extra clicks. Projects outside your group
are ignored, and no rules are created or changed automatically.
On **GitHub**, PRFlow mirrors the repositories you granted to the PRFlow App
and keeps the list in sync when you add or remove repositories on the
installation.
## Removed, renamed, and re-added repositories
Rules identify a repository by its full name, not by an internal record, so a
specific-repository rule survives the repository leaving and coming back β
for example when a repo is removed from your GitHub installation, or when you
disconnect and reconnect a provider. The rule simply starts matching again
the moment the repository reappears.
One caveat: a repository that comes back under a **new name** is a different
name, so the rule keeps looking correct but no longer matches. Edit the rule
and pick the repository again. (Names are matched exactly, so a
capitalization change counts as a rename too.)
# Comment sync
Source: https://docs.prflow.dev/comment-sync
Review comments on GitLab merge requests and GitHub pull requests appear as threaded replies under the Slack notification β new comments post to the thread, edits update in place, and deletions remove the reply.
PRFlow keeps one Slack message per merge request and updates it in place as
the MR progresses. **Comment sync** does the same for the review
conversation: every comment on the MR becomes a threaded reply under that
message, so the Slack thread mirrors the discussion as it happens.
Reviewers keep commenting where they always have β in GitLab or GitHub β and
teammates who live in Slack follow the whole conversation from the thread.
The channel itself stays clean: comments only ever go to the thread, never
posted as separate channel messages.
## How it works
Each synced comment is a threaded reply that preserves the comment text (up to
2,000 characters) and attributes it to the commenter's GitLab or GitHub
username. The thread stays in step with the source:
| When someoneβ¦ | β¦the Slack thread |
| ----------------- | ---------------------------------------- |
| posts a comment | gets a new reply under the MR message |
| edits a comment | shows the new text on the existing reply |
| deletes a comment | has that reply removed |
Sync is one-way, from GitLab/GitHub into Slack. PRFlow has read-only access
to your code platform, so replying in the Slack thread does not post
anything back to the merge request.
## What gets synced
**GitLab** β comments on merge requests, both general comments and inline
diff comments. Notes on issues, commits, and snippets are ignored. Comments
arrive via the `Note Hook` webhook, and PRFlow's polling fallback catches
comments a missed webhook delivery would have dropped.
**GitHub** β pull request comments from the Conversation tab, inline review
comments on the diff, and the summary body submitted with a review. Comments
on plain issues are ignored. Approved and changes-requested summaries include
that state in text in the reply, while the outcome also updates the PR message.
Editing a review summary updates its reply; GitHub sends no event when a
review is deleted, so the delete row above applies to comments, not summaries.
## Good to know
* **Each routing rule controls new replies.** In the rule editor, choose
**All comments** to post every comment, **Hide bot comments** to skip bots
PRFlow recognizes and marks Hidden, or **No comments** to create no new
replies. New rules start on Hide bot comments.
* **Changing modes is prospective.** A skipped comment is not backfilled if
you later choose a more permissive mode. Replies that were already posted
still receive edits and deletions, regardless of the rule's current mode.
* **The pull or merge request card stays authoritative.** Skipping a reply
does not stop the card from tracking review state, CI, or unresolved-thread
counts from GitHub or GitLab.
* **Comments need a parent message.** If an MR has no Slack message β for
example, the project was connected to PRFlow after the MR was opened, or
the comment was made while the MR was still a draft β the comment is not
posted, and it is not posted retroactively.
* **Exactly one reply per source object.** Duplicate or concurrently delivered
webhook events never produce a second reply. A GitHub review summary and its
inline comments are distinct source objects, so each appears as its own reply.
* See [data handling](/gitlab-integration#data-handling) for how PRFlow retains
notification data while delivering and synchronizing updates.
## Setup
Comment sync is available for every project you [map to a Slack channel](/channel-routing),
on every plan. Open a routing rule's Notifications section to choose its
comment mode. New to PRFlow? Start with the [quickstart](/quickstart).
Webhook events, API calls, data handling, and security details.
The one-click GitHub App: events, permissions, and setup.
Questions? Reach us at [hello@prflow.dev](mailto:hello@prflow.dev).
# FAQ
Source: https://docs.prflow.dev/faq
Answers to common PRFlow questions β setup and permissions, notifications that didn't arrive, Slack channels and threads, and disconnecting or uninstalling.
Short answers to the questions we get most, grouped by topic. Each answer
links to the page with the full detail. Can't find yours? Email
[hello@prflow.dev](mailto:hello@prflow.dev).
## Setup & permissions
### Why does PRFlow only ask for read-only access?
Everything PRFlow does β posting the Slack card, showing CI status, syncing
comments β only requires reading merge request, comment, and pipeline
metadata, so that is all the access it requests. On GitLab that is the single
[`read_api` scope](/gitlab-integration); on GitHub the App requests four
[read-only permissions](/github-integration#permissions-&-data) (Pull
requests, Checks, Issues, Metadata). PRFlow cannot modify anything on your
code platform, and it never reads repository code or diffs.
### GitHub is asking me to approve a new "Issues" permission for PRFlow. Why?
The PRFlow GitHub App added the read-only **Issues** permission in August
2026 to sync comments from a pull request's Conversation tab: GitHub
delivers those as issue comments, and an App can only receive them with
Issues access β there is no PR-scoped alternative. It is still read-only,
and PRFlow still has no access to repository code. Comments on plain issues
are received and immediately discarded. Your notifications keep working
while the request is pending; once an organization owner approves it,
conversation comments start appearing in the PR's Slack thread. Details in
[Permissions & data](/github-integration#permissions-&-data).
### Can I connect both GitLab and GitHub to the same workspace?
No β a PRFlow workspace connects one source provider, GitHub or GitLab, plus
one Slack workspace. If your team uses both platforms, create a separate
PRFlow organization for each; the organization switcher in the navbar moves
you between them, and more than one organization can post into the same
Slack workspace.
### I added PRFlow to Slack first, from an "Add to Slack" link. Did I do it in the wrong order?
No harm done β no partial or dangling Slack connection is created. PRFlow
works source-first: you connect a GitLab or GitHub repository, then add Slack
from within PRFlow, so a Slack-first install lands on a guided sign-in page
that explains exactly that. Create an account or sign in and continue from
the [quickstart](/quickstart).
### I installed the GitHub App from the GitHub Marketplace first. What now?
That path is fully supported: after installing, GitHub sends you to PRFlow to
link the installation to your workspace. Sign in (or sign up) and the
Connections page shows a "Finish connecting GitHub" prompt with GitHub
pre-selected β press **Connect GitHub** and you're linked. The end result is
identical to starting from inside PRFlow.
### Does PRFlow work with self-managed GitLab or GitHub Enterprise?
Self-managed GitLab works: any instance reachable over HTTPS, connected with
a `read_api` personal access token (gitlab.com additionally supports OAuth).
GitHub Enterprise Server is not supported β the
[GitHub App](/github-integration) works with github.com organizations and
personal accounts.
## When a notification doesn't arrive
### A merge request didn't show up in Slack. What should I check first?
Check whether events are reaching PRFlow at all: the webhook panel on
**Settings β Connections** shows **"Last event received N hours ago"** once
deliveries flow, or "No events received yet" before the first one β on
GitLab, press **Test** on the webhook and refresh the page to watch the
timestamp update. If events are arriving, check your
[notification rules](/channel-routing): rules are evaluated top to bottom and the
first match wins, so make sure an enabled rule matches the repository β and
check which rule is actually catching it.
### Why is there no message for my draft merge request?
By default, drafts stay silent: the card appears when the MR is marked
ready for review (on GitHub, the "Ready for review" action). To get drafts
in the channel, turn on the **Draft PRs** switch in the matching
[notification rule](/channel-routing).
### Why weren't MRs that were already open announced when I connected the project?
PRFlow only announces activity it sees after a project is connected β merge
requests opened earlier are not posted retroactively. If you want a card for
an MR that predates the connection, closing and reopening it posts one: a
reopen event creates the message just like an open does.
### A comment never appeared in the Slack thread. Why?
Comments need a parent message: if the MR has no Slack card β the project was
connected after the MR was opened, or the comment was made while the MR was
still a draft β the comment is not posted, and not retroactively either.
Also check it's something PRFlow syncs: GitLab notes on issues, commits, and
snippets are ignored, as are GitHub comments on plain issues. The full rules
are on the [comment sync](/comment-sync) page.
### GitLab shows webhook deliveries failing with a 404. What's wrong?
A 404 means the delivery URL doesn't match your workspace's unique webhook
endpoint β usually a typo from editing the URL by hand. PRFlow rejects
unrecognized webhook paths with 404 precisely so the mistake shows up in
GitLab's delivery log instead of looking like a success. Copy the URL again
from the webhook panel on **Settings β Connections** and update the webhook
in GitLab.
## Slack channels & threads
### Why doesn't a private channel show up in the channel picker?
PRFlow can only see private channels it has been invited to. Run
`/invite @PRFlow` in the channel, then refresh the page β the channel appears
in the picker like any other. Public channels never need an invite; the
`chat:write.public` scope covers them. More in the
[Slack integration](/slack-integration) reference.
### If I reply in the Slack thread, does my reply appear on the MR?
No β comment sync is one-way, from GitLab or GitHub into Slack. PRFlow has
read-only access to your code platform, so nothing written in Slack is
posted back. Reply on the merge request itself and your comment will be
synced into the thread for everyone following along in Slack.
## Disconnecting & uninstalling
### What happens if I uninstall PRFlow from my Slack workspace?
PRFlow is notified immediately and disconnects on its side: the stored
access token is deleted and posting stops. If the Slack workspace was
connected to more than one PRFlow organization, every one of them is
disconnected β no organization is left showing "Connected" with a dead
token.
### What happens if I disconnect Slack from inside PRFlow?
**Disconnect** on the Slack card (admin-only, with a confirmation step)
removes the connection and also revokes the bot token at Slack immediately,
so it doesn't linger β unless another of your PRFlow organizations shares
the same Slack workspace, in which case revocation is safely skipped and
only your connection is removed. Your repository and channel-mapping setup
is kept, and notifications resume when you reconnect.
### What happens if I uninstall the PRFlow GitHub App on GitHub?
The App reports its own lifecycle events, so uninstalling it from GitHub's
settings cleanly disconnects the integration in PRFlow automatically β there
is nothing to clean up on the PRFlow side. Changing the App's repository
selection works the same way: PRFlow picks up added and removed repositories
on its own.
### What happens if I disconnect GitHub from inside PRFlow?
Disconnect on the Connections page removes the integration and every
repository tracked under it. It does not uninstall the App on GitHub's
side β if you no longer want it installed, remove it from GitHub's App
settings too.
### What happens if I disconnect GitLab from inside PRFlow?
PRFlow deletes its stored copy of your credential (OAuth tokens or personal
access token), releases the claim on your top-level group, and removes its
record of your projects. Because PRFlow has no write access to GitLab, two
things are left for you: the webhook you created stays configured in GitLab
until you remove it there, and a personal access token remains valid until
you revoke it in GitLab.
### What happens when my GitLab token expires or is revoked?
PRFlow validates a personal access token when you save it (scope, expiry,
and revocation status) and refreshes OAuth tokens automatically before they
expire. If GitLab starts rejecting the credential anyway β a revoked or
expired token answers 401/403 β the connection is flagged as unhealthy on
your dashboard so you can rotate it. Re-entering a new token works without
disconnecting first, even if you also spell out `https://gitlab.com` as the
instance URL.
## Still stuck?
Email [hello@prflow.dev](mailto:hello@prflow.dev) β include a link to the
MR/PR and the channel you expected the notification in, and we'll get you
sorted.
# GitHub integration
Source: https://docs.prflow.dev/github-integration
How PRFlow connects to GitHub through a one-click GitHub App β available on the GitHub Marketplace β which pull request events it reacts to, and what it sends to Slack.
## Overview
PRFlow delivers GitHub pull request notifications to Slack. For each pull
request it posts **one Slack message that updates in place** as the PR
progresses β CI check status, reviews, and merge state all appear on the
original message, and PR comments are synced into a Slack thread under it.
Unlike the [GitLab integration](/gitlab-integration), there is nothing to
configure on the GitHub side beyond installing the **PRFlow GitHub App**:
the App delivers webhook events automatically and grants PRFlow its
(read-only) API access in one step. PRFlow works with **github.com**
organizations and personal accounts; GitHub Enterprise Server is not
supported.
The App is published on the GitHub Marketplace as
[**PRFlow for Slack**](https://github.com/marketplace/prflow-for-slack),
listed under Code review and Chat with a free Starter plan.
## Setup
1. In PRFlow, open **Settings β Connections** and select **GitHub** as the
source provider. A workspace connects one source provider β GitHub or
GitLab, not both at once.
2. Click **Install GitHub App**. GitHub asks you to pick the account or
organization and which repositories the App may see (all, or a selected
list), and shows the read-only permissions it requests β the full list,
and why each one is needed, is in
[Permissions & data](#permissions-&-data) below.
3. Back in PRFlow, connect Slack if you haven't yet, then map repositories
to Slack channels from the dashboard.
That's the whole setup β no webhook URLs, no secrets to paste. You can
change the repository selection any time from GitHub's App settings; PRFlow
picks the change up automatically.
You can also start from the
[GitHub Marketplace listing](https://github.com/marketplace/prflow-for-slack)
and install the App there first. GitHub then sends you to PRFlow to link the
installation to your workspace β sign in (or sign up) and press **Connect
GitHub** on the Connections page to finish. The end result is identical to
starting from inside PRFlow.
## How it works
### Events PRFlow reacts to
The GitHub App subscribes to pull request activity. Events for repositories
you haven't mapped to a Slack channel are acknowledged and discarded.
| Event | What PRFlow does |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pull_request` | `opened`, `reopened`, and `ready_for_review` post the PR message; `edited`, `synchronize`, `review_requested`, and `converted_to_draft` update it in place; `closed` marks the PR merged or closed. Other actions (labeled, assigned, β¦) are ignored. Draft PRs are skipped until `ready_for_review`, unless the rule's [**Draft PRs** switch](/channel-routing) is on. |
| `pull_request_review` | A submitted review updates the message's review state β **approved** or **changes requested** β and posts a non-empty review summary as a threaded reply. Edited summaries update an existing reply. |
| `issue_comment`, `pull_request_review_comment` | Conversation comments on a PR and inline review comments on the diff are posted as threaded replies under the PR message. Edited comments are updated in place; deleted comments are removed from the Slack thread. Comments on plain issues are ignored. |
| `check_suite` / `check_run` | Update the CI status shown on the PR message, aggregated across check suites β GitHub Actions and any other check provider. Only the newest run of each check counts, so a re-run that passes flips the card to β
even if an earlier run failed. |
| `installation`, `installation_repositories` | Track the App's lifecycle: installing, uninstalling, and changing the repository selection. Uninstalling the App from GitHub cleanly disconnects the integration. |
### Slack message model
The Slack side is identical to the GitLab integration: a 1:1 mapping between
a pull request and a Slack message, updated in place via `chat.update`, with
merge-requirement rows that flip from π΄ to π’ as CI, approvals, and
mergeability are satisfied. On GitHub the merge requirements are read from
the repository's **branch rulesets**; repositories using classic branch
protection (which the App's read-only permissions cannot inspect) get a
conservative fallback that never overstates what is required.
**Comment sync**: each PR comment becomes a threaded reply under the PR
message, quoting the comment text (truncated to 2,000 characters) with the
commenter's username. Comment edits on GitHub update the existing thread
reply; comment deletions remove it. PRFlow retains the comment text along
with the comment ID and the Slack message reference β see
[data handling](/gitlab-integration#data-handling), which applies equally to
GitHub.
## Permissions & data
The App requests four **read-only** permissions:
| Permission | Why PRFlow needs it |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pull requests: read | PR metadata for notifications, and review state when rendering the message. |
| Checks: read | CI status from check suites and check runs. |
| Issues: read | Conversation (non-code) comments on a pull request β GitHub delivers these as issue comments, and reading them requires this permission. Comments on plain issues are received and immediately discarded. |
| Metadata: read | Repository names for the repository picker, and branch rulesets for the merge-requirement rows. |
The Issues permission was added in August 2026 to carry conversation
comments β there is no PR-only way to receive them from GitHub. If you
installed the App before then, GitHub shows organization owners a one-time
request to approve the new permission. Everything else keeps working while
the request is pending, and conversation comments start syncing the moment
it is accepted.
PRFlow has no write access and no Contents permission, so it cannot read
repository code or diffs, and it cannot modify anything on GitHub. Every
webhook delivery is signature-verified (`X-Hub-Signature-256`) before it is
processed. As with GitLab, only pull request, review, comment, and check
metadata is handled β see the GitLab page's
[data handling section](/gitlab-integration#data-handling) for the storage
and Slack-delivery details, which apply equally here.
## Disconnecting
Either side works: **Disconnect** on the PRFlow Connections page removes the
integration and every repository tracked under it (it does not uninstall the
App on GitHub's side), or uninstall the App from GitHub's settings and
PRFlow disconnects automatically.
## Support
Questions about the integration or a notification that didn't arrive? Email
[hello@prflow.dev](mailto:hello@prflow.dev) and we'll get you sorted.
# GitLab integration
Source: https://docs.prflow.dev/gitlab-integration
How PRFlow connects to GitLab, which webhook events and API calls it uses, what it sends to Slack, and exactly what merge request data it handles along the way.
## Overview
PRFlow delivers GitLab merge request notifications to Slack. For each merge
request it posts **one Slack message that updates in place** as the MR
progresses β CI/CD pipeline status, approvals, and merge state all appear on
the original message, and GitLab review comments are synced into a Slack
thread under it.
The integration has three parts:
1. **GitLab webhooks** β your GitLab group or projects send merge request,
comment, and pipeline events to a per-workspace PRFlow endpoint in real
time.
2. **Read-only GitLab API access** β PRFlow uses a credential with the
`read_api` scope to list your projects during setup, to look up approval
and discussion state when rendering notifications, and to run a polling
fallback that catches anything a missed webhook would have dropped.
3. **Slack Web API** β PRFlow posts and updates messages in the channels you
map, using a Slack bot token obtained via OAuth.
```text theme={null}
GitLab ββ webhooks (MR / note / pipeline events) βββΆ PRFlow ββ chat.postMessage /
βββ read-only API calls (read_api) ββββββββββ chat.update βββΆ Slack
```
PRFlow works with **gitlab.com** (OAuth or personal access token) and
**self-managed GitLab** instances reachable over HTTPS (personal access
token). It never requests write access to GitLab and never reads repository
code or diffs β only merge request, comment, and pipeline metadata.
## How it works
### Webhook events consumed
PRFlow exposes a unique webhook endpoint per workspace
(`https://app.prflow.dev/webhooks/gitlab/`). It processes three
GitLab event types, identified by the `X-Gitlab-Event` header. Any other
event type is acknowledged with HTTP 200 and discarded, so you can safely
enable extra event toggles in GitLab without side effects.
| GitLab event | What PRFlow does |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Merge Request Hook` | `open` / `reopen` post the MR message; `update`, `close`, and `merge` update it in place; `approval` / `approved` and `unapproval` / `unapproved` trigger an approval-state refresh (verified against the GitLab approvals API before the message shows "approved"). Draft MRs are skipped until they leave draft state, unless the rule's [**Draft PRs** switch](/channel-routing) is on. |
| `Note Hook` | Comments on merge requests (`noteable_type: MergeRequest`) are posted as threaded replies under the MR message. Edited comments are updated in place; deleted comments are removed from the Slack thread. Notes on issues, commits, or snippets are ignored. |
| `Pipeline Hook` | Updates the CI status shown on the MR message. Recognized statuses: `success`, `failed`, `running`, `pending`, `canceled`, `skipped`, `manual`. Pipeline updates stop once an MR is merged or closed. |
Events for projects you have not added in PRFlow are acknowledged and
ignored. This makes a single group-level webhook safe: GitLab can fan out
events for every project in the group, and PRFlow only acts on the ones you
mapped to a Slack channel.
### GitLab API calls
All data calls are read-only `GET` requests under the `read_api` scope. The
only `POST` PRFlow sends to GitLab is the standard OAuth token exchange and
refresh on `/oauth/token`. PRFlow never modifies anything in your GitLab
instance.
| Endpoint | Purpose |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /user` | Identify the connected account during OAuth and token validation. |
| `GET /groups?top_level_only=true` | Resolve the top-level GitLab group your workspace is scoped to during setup. |
| `GET /groups/:id/projects` | List projects (including subgroups) when you add repositories. Only projects where the credential has at least Developer access are listed. |
| `GET /projects/:id`, `GET /groups/:id` | Fetch project and group metadata (name, path, avatar) for the dashboard. |
| `GET /projects/:id/merge_requests/:iid/approvals` | Check approvals remaining, so the message can show an accurate "needs approvals" indicator and only flips to "approved" when approval rules are actually satisfied. |
| `GET /projects/:id/merge_requests/:iid/discussions` | Count unresolved discussions for the "has unresolved discussions" indicator. |
| `GET /projects/:id/merge_requests`, `GET .../merge_requests/:iid/notes` | Background polling fallback: PRFlow periodically diffs merge request state via the API and emits the same message updates if a webhook delivery was missed. |
| `GET /personal_access_tokens/self` | Validate a personal access token when you save one: scope check, expiry, and revocation status. |
| `POST /oauth/token` | OAuth authorization-code exchange and automatic refresh-token rotation (gitlab.com OAuth connections). |
### Slack message model
PRFlow keeps a **1:1 mapping between a merge request and a Slack message**.
The first qualifying event posts the message to the channel you mapped; every
later event updates that same message (Slack `chat.update`) instead of
posting a new one. If a project is mapped to multiple channels, each channel
gets at most one message per MR, and updates stay in the channel where the
message was originally posted.
The message shows:
* a status emoji for the MR state (new / approved π / merged β
/ closed β),
* the MR number and title, linked to the MR in GitLab,
* author display name and username, target branch, and project name,
* inline CI status when pipeline events arrive (β
passed, β failed,
π running, π« canceled, βοΈ skipped, π manual),
* actionable blockers from GitLab's `detailed_merge_status` β e.g. "Needs
approvals", "Has unresolved discussions", "CI failed", "Needs rebase",
"Has merge conflicts".
**Comment sync**: each GitLab comment on the MR becomes a threaded reply
under the MR message, quoting the comment text (truncated to 2,000 characters)
with the commenter's username. Comment edits in GitLab update the existing
thread reply; comment deletions remove it. If an MR has no parent message in
Slack (for example, the project was connected after the MR was opened),
comments are not posted.
**Ordering and duplicates**: PRFlow timestamps each event and rejects stale,
out-of-order updates at the database layer before touching Slack, and uses
atomic claim records so concurrent webhook deliveries never produce duplicate
messages. MRs created before a project was connected to PRFlow are not
announced retroactively.
## Setup guide
Setup takes a few minutes in the PRFlow web app at
[app.prflow.dev](https://app.prflow.dev). You connect GitLab, point a webhook
at PRFlow, connect Slack, and map projects to channels.
Sign in at [app.prflow.dev](https://app.prflow.dev) and create or select a
workspace. Each PRFlow workspace connects to one GitLab organization
(top-level group) and one Slack workspace.
From **Home** or **Settings β Connections**, choose your instance type:
* **gitlab.com** β connect via OAuth (PRFlow requests only the `read_api`
scope; tokens auto-refresh), or save a personal access token instead.
* **Self-managed GitLab** β enter your instance root URL (HTTPS required;
PRFlow appends `/api/v4` automatically) and save a personal access
token. OAuth is not available for self-managed instances.
Personal access tokens must have the `read_api` scope and access to the
GitLab group you want PRFlow to use. PRFlow validates the token on save β
scope, expiry, and revocation β and recommends a dedicated service-account
token over a personal one to reduce operational risk. Token health
(expiring, expired, revoked) is surfaced on the dashboard afterwards.
During connection, PRFlow resolves the **top-level GitLab group** your
credential can access and binds the workspace to it. One GitLab
organization maps to one PRFlow workspace β connecting the same GitLab
group to a second workspace is rejected.
PRFlow shows your workspace's unique webhook URL under
**Settings β Connections β GitLab Webhook**. In GitLab, add it as either:
* **Group webhook (recommended)** β add the URL once on your top-level
group (**Group β Settings β Webhooks**). Covers all projects in the
group. Requires the Owner role and GitLab Premium or Ultimate.
* **Per-project webhooks** β add the same URL to each project
(**Project β Settings β Webhooks**). Works on every GitLab tier,
including Free. Requires Maintainer or Owner on each project.
Enable the **Merge request events**, **Comment events**, and **Pipeline
events** triggers, and keep **SSL verification** on. Optionally set a
**Secret token** in GitLab and save the same value in PRFlow β PRFlow then
verifies the `X-Gitlab-Token` header on every delivery.
Authorize PRFlow's Slack app via OAuth. PRFlow requests four bot scopes:
`chat:write`, `chat:write.public`, `channels:read`, and `groups:read` β
enough to list channels and post/update messages, nothing more.
Click **+ New rule** on the dashboard, pick the destination Slack channel
(PRFlow fills in the rule name for you), and choose what routes there: a
specific project, a wildcard pattern on the full GitLab path such as
`acme/frontend/*`, or every repository as a catch-all. Rules are checked
top to bottom and the first match wins β see
[Notification rules](/channel-routing) for the details. From then on, every
merge request in the matched projects posts itself to the rule's channel
(drafts wait until they're marked ready unless the rule's **Draft PRs**
switch is on).
**GitLab compatibility:** gitlab.com on any tier (group webhooks need
Premium or Ultimate; per-project webhooks work on Free), and self-managed
GitLab instances reachable over HTTPS. Self-managed instances behind a
firewall need outbound HTTPS to `app.prflow.dev` for webhooks; PRFlow's API
polling additionally requires the instance to be reachable from the
internet.
## Data handling
When you connect PRFlow, **merge request metadata leaves your GitLab
instance**: GitLab transmits it to PRFlow via webhooks and API responses, and
PRFlow forwards a subset of it into the Slack workspace you connect. This
section spells out exactly what moves and what is stored. See the
[privacy policy](https://prflow.dev/privacy.html) for the full legal terms.
### Data received from GitLab
From the three webhook event types and the API calls listed above, PRFlow
receives:
* Project metadata: ID, name, path, web URL.
* Merge request metadata: IID, title, description, state, action,
source/target branch, author, draft flag, detailed merge status,
timestamps, web URL.
* Comment (note) data: note ID, comment text, author username, action, URL.
* Pipeline data: pipeline ID, status, ref, duration.
* Approval and discussion summaries: approvals remaining, unresolved
discussion count.
* Connected-account info: GitLab user ID, username, display name, avatar URL;
group names and paths.
PRFlow never requests repository file contents, diffs, or commit contents.
The `read_api` credential is used exclusively for the endpoints documented
above.
### Data sent to Slack
Notifications in your Slack channels contain:
* MR number, title, and a link to the MR in GitLab,
* author display name and username, target branch, project name,
* CI pipeline status, approval/discussion indicators, and merge-status
labels,
* comment text excerpts (up to 2,000 characters) and commenter usernames in
thread replies.
### What PRFlow stores
* Connection records: your workspace, the connected GitLab account
ID/username, the bound top-level group, and the instance URL for
self-managed GitLab.
* Credentials: OAuth access/refresh tokens, personal access tokens, and the
optional webhook secret β all encrypted at rest with AES-256-GCM.
* Repository and channel-mapping configuration for the projects you add.
* Per-MR metadata needed to keep the Slack message current: title, state,
branches, web URL, author username and display name, pipeline status,
detailed merge status, approval/discussion counts, and timestamps.
* Slack message references (channel ID, message timestamp) for each MR
message and each synced comment.
* Comment text, comment URL, and author username for each synced comment,
alongside the note ID and Slack message reference.
**Why comment text is retained.** PRFlow delivers Slack messages through a
durable queue: a reply is recorded first and posted afterwards, then retried
if Slack is unavailable, so the text has to survive between accepting the
comment and delivering it. Keeping it also lets an edit in GitLab or GitHub
update the existing reply, and a deletion remove it, without re-fetching
from the source. Only comments PRFlow syncs to Slack are stored this way.
## Permissions & security
### GitLab scopes
| Scope | Why PRFlow needs it |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `read_api` | The single scope PRFlow requests β for OAuth and for personal access tokens alike. It covers listing groups and projects during setup, reading MR approval and discussion state, and the polling fallback. No write scope is ever requested; PRFlow cannot modify code, MRs, settings, or anything else in GitLab. |
### Slack scopes
| Scope | Why PRFlow needs it |
| ------------------- | ---------------------------------------------------------------------------------- |
| `chat:write` | Post the MR message, update it in place, and manage threaded comment replies. |
| `chat:write.public` | Post to public channels without requiring a manual bot invite to each one. |
| `channels:read` | List public channels in the channel picker when you map a project. |
| `groups:read` | List private channels the bot has been added to, so they appear in the picker too. |
### Webhook endpoint security
* Each workspace gets a unique, unguessable webhook URL; deliveries are
matched to exactly one workspace.
* When you configure a secret token, PRFlow verifies the `X-Gitlab-Token`
header on every delivery using a constant-time comparison and rejects
mismatches with HTTP 401. The secret is stored encrypted. Leaving the
secret blank (URL-only mode) is supported but the secret is recommended.
* Events whose project host does not match the connected GitLab instance are
rejected.
* Events for projects you have not added are acknowledged and discarded
without processing.
* Webhook payloads are capped at 10 MB to prevent resource-exhaustion abuse.
### Credentials and transport
* The gitlab.com OAuth flow uses the standard authorization-code grant with
CSRF state validation (state values are stored as SHA-256 hashes and expire
after 10 minutes).
* GitLab and Slack tokens are encrypted at rest with AES-256-GCM and are
never written to logs.
* OAuth access tokens are refreshed automatically before expiry; 401/403
responses from GitLab flag the credential as unhealthy on your dashboard so
you can rotate it.
* All traffic β GitLab webhooks in, GitLab API calls out, Slack API calls
out β runs over HTTPS. Self-managed instance URLs must be HTTPS.
## Support
Questions about the integration, setup help, or a webhook that isn't firing?
Email [hello@prflow.dev](mailto:hello@prflow.dev) and we'll get you sorted.
# Welcome to PRFlow
Source: https://docs.prflow.dev/index
Merge request and pull request notifications in Slack, for GitLab and GitHub β one updating message per MR/PR with CI/CD status and threaded comment sync.
PRFlow replaces the native GitLab and GitHub Slack integrations for
code-review notifications. Instead of flooding channels with separate messages
for every event, PRFlow posts a **single message per merge request or pull
request** that updates in place as it progresses β CI/CD status, approvals,
and merge all appear on the original message, and review comments go to a
Slack thread.
## Key features
* **[One updating Slack message](/mr-states) per merge request or pull request** β not 10+ separate messages, with a colored rail showing each MR/PR's state at a glance
* **CI/CD status** displayed inline on the notification β GitLab pipelines and GitHub Actions
* **[Review comments synced](/comment-sync)** to a Slack thread, with edits updated in place β on both GitLab and GitHub
* Works with **gitlab.com, self-managed GitLab, and GitHub**
* **Read-only access** β GitLab `read_api` scope, or the read-only PRFlow GitHub App; no write permissions
* Posts to **your existing Slack channels** β no ephemeral channels
Connect GitLab or GitHub and Slack and get your first notification in a
few minutes.
Free for teams of up to 3 users. All features included on every plan.
## How it works
PRFlow is a multi-tenant SaaS: each workspace connects one source provider β a
GitLab organization (top-level group) or GitHub via the PRFlow GitHub App,
published on the GitHub Marketplace as
[PRFlow for Slack](https://github.com/marketplace/prflow-for-slack) β and
one Slack workspace. The source sends webhook events to PRFlow, PRFlow keeps
one Slack message per merge request or pull request up to date, and comments
flow into the message's thread.
Questions? Reach us at [hello@prflow.dev](mailto:hello@prflow.dev).
# MR card states
Source: https://docs.prflow.dev/mr-states
What the colored rail on PRFlow's Slack card means: blue, amber, red, green, purple, and grey, state by state.
PRFlow posts one Slack card per merge request and keeps updating that same
message as the MR moves through its lifecycle, while review comments join it
as [threaded replies](/comment-sync). The colored rail on the card's left
edge summarizes the state at a glance, so you can scan a channel without
reading a single row. The same card and colors apply to GitHub pull requests.
| Rail | State | What it means |
| --------- | -------------- | ----------------------------------------------- |
| π΅ Blue | Under review | Default state, nothing known to block the merge |
| π Amber | In flux | Draft, pipeline running, or merge scheduled |
| π΄ Red | Blocked | At least one required merge gate is failing |
| π’ Green | Ready to merge | Every required gate is confirmed passing |
| π£ Purple | Merged | The MR was merged, the card's final state |
| β« Grey | Closed | Closed without merging, comes back if reopened |
## Reading the requirement rows
The card body shows one row per merge requirement that is actually enabled for
the repository: required CI, approvals (with the required count), an
up-to-date branch, resolved review threads, and merge conflicts. Each row
flips independently as events arrive:
* π’ the requirement is confirmed passing
* π΄ the requirement is failing and blocks the merge
* π in progress right now (a running pipeline)
* βͺ pending (a pipeline that exists but has not started)
* β»οΈ unknown, PRFlow has no signal yet; unknown is never treated as passing
A draft PR's card shows a π **Draft** row while the draft flag is set
(drafts post when the rule's [**Draft PRs** switch](/channel-routing) is on).
The rail color is derived from those rows plus the MR's lifecycle, as follows.
## Blue: under review
The default state. Nothing is known to block the merge, but not every
requirement is confirmed passing yet. A just-opened MR usually starts blue:
the pipeline has not run and nobody has reviewed it. Rows showing β»οΈ or βͺ
keep the card blue rather than green, because PRFlow never counts an unknown
as a pass.
## Amber: in flux
The state is changing right now. The rail turns amber while the MR is a
draft, while a pipeline is actively running, or while a merge is scheduled to
happen automatically (for example "merge when pipeline succeeds"). Amber
resolves itself: once the pipeline finishes or the draft is marked ready, the
card moves to blue, red, or green.
## Red: blocked
At least one required merge gate is failing. Failed or canceled CI, missing
approvals, unresolved review threads, a branch that needs a rebase, and merge
conflicts all turn the rail red, and the failing rows name exactly what is
left to fix. Less common blockers (blocked by another MR, security policy
violations, a missing Jira issue, and similar) also turn the rail red and
appear as their own row. A failing gate that is not required, such as an
optional pipeline, never turns the card red.
## Green: ready to merge
Every required gate is confirmed passing: the MR is mergeable right now.
Green is earned, never assumed. If any required gate is still unknown, the
card stays blue instead of guessing.
## Purple: merged
The MR was merged. The requirement rows collapse into a single line naming
the target branch, so the card becomes a compact record of the MR in the
channel. Merged is the card's only final state: a merged MR cannot be
reopened, so the card never changes again.
## Grey: closed
The MR was closed without being merged: the rows collapse into a single
β Closed line. Unlike merged, closed is not final. If the MR is reopened,
the card comes back to life and the rail returns to whatever color the
reopened MR's state calls for.
***
Questions about a state you're seeing? Email
[hello@prflow.dev](mailto:hello@prflow.dev).
# Scheduled digest
Source: https://docs.prflow.dev/pr-digest
A recurring Slack post that lists a channel's open PRs, grouped by what each one is waiting on.
## What it posts
On the schedule you pick, PRFlow posts a summary of every open PR routed to a
Slack channel, from GitLab and GitHub alike. Each line links the PR and shows
why it is still open, how old it is, and who wrote it. PRs are sorted into
three groups:
* π΄ **Needs attention**: failing CI, merge conflicts, changes requested,
unresolved discussions, or a blocked merge
* π‘ **Waiting on review**: approvals missing or CI still running
* π’ **Ready to merge**: approved and green
PRs with no activity for longer than the stale threshold are flagged with π¨.
## Turning it on
The digest lives inside the [notification rule](/channel-routing) that posts to
the channel. On the dashboard, open the rule's editor β **Edit** on an
existing rule, or while creating one β and switch on **Scheduled digest**.
The schedule, filter, and delivery settings unfold below the toggle.
## Settings
* **Schedule**: the days and time of day the digest posts. The timezone
defaults to your workspace setting and can be overridden per channel.
* **Filters**: hide draft PRs (independent of the rule's
[**Draft PRs** switch](/channel-routing)), hide bot-authored PRs
(Dependabot, Renovate, and similar), and skip recently opened PRs, either
anything opened today or anything younger than a number of hours or days. **Flag PRs as stale**
sets how many days of inactivity earn the π¨ marker.
* **Delivery**: an empty digest is skipped by default. Turn on **Post even
when there are no open PRs** to get a short all-clear message instead.
Digest settings belong to the channel, so everything a rule routes there
shares one digest. They also stay with the channel: deleting a rule and later
creating a new one for the same channel brings the saved settings back.
# Quickstart
Source: https://docs.prflow.dev/quickstart
Connect GitLab or GitHub and Slack, map repositories to channels, and get your first merge request notification in a few minutes.
Setup takes a few minutes in the PRFlow web app at
[app.prflow.dev](https://app.prflow.dev): you connect a source provider β
GitLab or GitHub β connect Slack, and map repositories to Slack channels.
## Pick your provider
A workspace connects one source provider. The Slack side is identical either
way; only the provider connection differs:
* **[GitLab](#set-up-with-gitlab)** connects via OAuth (gitlab.com) or a
`read_api` personal access token, plus a webhook you point at PRFlow.
Works with gitlab.com and self-managed instances.
* **[GitHub](#set-up-with-github)** is a one-click install of the read-only
[PRFlow GitHub App](/github-integration) β no webhook URLs, no tokens or
secrets to paste. Works with github.com organizations and personal
accounts.
## Set up with GitLab
Sign in at [app.prflow.dev](https://app.prflow.dev) and create or select a
workspace. Each PRFlow workspace connects to one GitLab organization
(top-level group) and one Slack workspace.
From **Home** or **Settings β Connections**, choose your instance type:
* **gitlab.com** β connect via OAuth (PRFlow requests only the `read_api`
scope; tokens auto-refresh), or save a personal access token instead.
* **Self-managed GitLab** β enter your instance root URL (HTTPS required;
PRFlow appends `/api/v4` automatically) and save a personal access
token. OAuth is not available for self-managed instances.
Personal access tokens must have the `read_api` scope and access to the
GitLab group you want PRFlow to use. PRFlow validates the token on save β
scope, expiry, and revocation β and recommends a dedicated service-account
token over a personal one to reduce operational risk. Token health
(expiring, expired, revoked) is surfaced on the dashboard afterwards.
During connection, PRFlow resolves the **top-level GitLab group** your
credential can access and binds the workspace to it. One GitLab
organization maps to one PRFlow workspace β connecting the same GitLab
group to a second workspace is rejected.
PRFlow shows your workspace's unique webhook URL under
**Settings β Connections β GitLab Webhook**. In GitLab, add it as either:
* **Group webhook (recommended)** β add the URL once on your top-level
group (**Group β Settings β Webhooks**). Covers all projects in the
group. Requires the Owner role and GitLab Premium or Ultimate.
* **Per-project webhooks** β add the same URL to each project
(**Project β Settings β Webhooks**). Works on every GitLab tier,
including Free. Requires Maintainer or Owner on each project.
Enable the **Merge request events**, **Comment events**, and **Pipeline
events** triggers, and keep **SSL verification** on. Optionally set a
**Secret token** in GitLab and save the same value in PRFlow β PRFlow then
verifies the `X-Gitlab-Token` header on every delivery.
Authorize PRFlow's Slack app via OAuth. PRFlow requests four bot scopes:
`chat:write`, `chat:write.public`, `channels:read`, and `groups:read` β
enough to list channels and post/update messages, nothing more.
Click **+ New rule** on the dashboard, pick the destination Slack channel
(PRFlow fills in the rule name for you), and choose what routes there: a
specific project, a wildcard pattern on the full GitLab path such as
`acme/frontend/*`, or every repository as a catch-all. Rules are checked
top to bottom and the first match wins β see
[Notification rules](/channel-routing) for the details. From then on, every
merge request in the matched projects posts itself to the rule's channel
(drafts wait until they're marked ready unless the rule's **Draft PRs**
switch is on).
**GitLab compatibility:** gitlab.com on any tier (group webhooks need
Premium or Ultimate; per-project webhooks work on Free), and self-managed
GitLab instances reachable over HTTPS. Self-managed instances behind a
firewall need outbound HTTPS to `app.prflow.dev` for webhooks; PRFlow's API
polling additionally requires the instance to be reachable from the
internet.
For the technical details behind these steps β webhook events consumed, API
endpoints called, and how data is handled β see the
[GitLab integration](/gitlab-integration) reference.
## Set up with GitHub
1. Sign in at [app.prflow.dev](https://app.prflow.dev), open
**Settings β Connections**, and select **GitHub** as the source provider.
2. Click **Install GitHub App**. GitHub asks which account or organization
to install to and which repositories the App may see, and shows the
read-only permissions it requests β the
[full list with reasons](/github-integration#permissions-&-data) is on the
GitHub integration page.
3. Back in PRFlow, [connect Slack](/slack-integration) and map repositories
to Slack channels from the dashboard.
That's the whole setup β no webhook URLs and no secrets to paste. The
[GitHub integration](/github-integration#setup) reference covers the
details, including starting from the GitHub Marketplace listing instead.
Questions or trouble getting set up? Check the [FAQ](/faq) or email
[hello@prflow.dev](mailto:hello@prflow.dev).
# Slack integration
Source: https://docs.prflow.dev/slack-integration
How PRFlow connects to Slack, which channels it can post to, the four bot scopes it requests, what data reaches your workspace, and how disconnecting or uninstalling works.
## Overview
PRFlow delivers GitLab merge request and GitHub pull request notifications to
Slack. For each MR/PR it posts **one Slack message that updates in place** as
the work progresses β CI status, approvals, and merge state all appear on the
original card instead of new messages piling up β and review comments are
synced into a Slack thread under it, so the channel stays readable.
Slack is the **destination** side of PRFlow. Notifications originate from a
source provider β [GitLab](/gitlab-integration) or
[GitHub](/github-integration) β so a workspace needs a source connected
before the Slack connection has anything to deliver. Each PRFlow workspace
connects to one Slack workspace.
## Setup
You connect Slack from inside PRFlow, via OAuth:
1. Sign in at [app.prflow.dev](https://app.prflow.dev) and connect a source
provider first β [GitLab](/gitlab-integration) or
[GitHub](/github-integration).
2. From **Home** or **Settings β Connections**, click **Connect Slack** and
authorize the app in the Slack consent screen. PRFlow requests the four
bot scopes listed below β enough to list channels and post/update its own
messages, nothing more.
3. Map repositories to Slack channels from the dashboard. From then on, every
qualifying MR/PR posts its card to the mapped channel.
Starting from Slack instead β an **Add to Slack** link or Slack's app
directory β also works: the install lands on a guided sign-in page that
explains the order of operations (connect a repository first, then add
Slack from within PRFlow). No partial or dangling Slack connection is
created along the way; you simply create an account or sign in and continue
setup from the right starting point.
## How it works
### Which channels PRFlow can post to
* **Public channels** β PRFlow can post to any public channel without a
manual invite (that is what the `chat:write.public` scope is for), and
every public channel appears in the channel picker.
* **Private channels** β PRFlow can only see and post to a private channel
after someone runs `/invite @PRFlow` in it. Once the bot is a member, the
channel shows up in the channel picker like any other.
PRFlow never reads channel messages: it requests no history scopes and no
user scopes, so it cannot see what anyone writes in your channels or DMs. It
only posts and updates its own messages.
### Posting model
* **One card per MR per channel.** The first qualifying event posts the
message; every later event updates that same message (Slack `chat.update`)
instead of posting a new one. If a repository is mapped to multiple
channels, each channel gets at most one message per MR, and updates stay in
the channel where the message was originally posted.
* **Comments as threaded replies.** Each review comment becomes a reply in
the card's thread, quoting the comment text (truncated to 2,000 characters)
with the commenter's username. Comment edits update the existing reply;
comment deletions remove it.
## Permissions & security
PRFlow requests exactly four **bot** scopes β no user scopes, no message
history:
| Scope | Why PRFlow needs it |
| ------------------- | ---------------------------------------------------------------------------------- |
| `chat:write` | Post the MR message, update it in place, and manage threaded comment replies. |
| `chat:write.public` | Post to public channels without requiring a manual bot invite to each one. |
| `channels:read` | List public channels in the channel picker when you map a project. |
| `groups:read` | List private channels the bot has been added to, so they appear in the picker too. |
The Slack bot token is encrypted at rest with AES-256-GCM and is never
written to logs, and all Slack API traffic runs over HTTPS.
### Data sent to Slack
Notifications in your Slack channels contain:
* MR/PR number, title, and a link back to GitLab or GitHub,
* author display name and username, target branch, and project name,
* CI status, approval/review indicators, and merge-status labels,
* comment text excerpts (up to 2,000 characters) and commenter usernames in
thread replies.
**Comment text is retained** for the comments PRFlow syncs, alongside the
comment ID and the Slack message reference: delivery is durable, so a reply
is recorded before it is posted and retried if Slack is unavailable, and an
edit at the source updates the existing reply without re-fetching it. For
the full picture of what PRFlow receives from the source provider and what
it stores, see the GitLab page's
[data handling section](/gitlab-integration#data-handling), which applies
equally to GitHub.
## Disconnecting
Either side works:
* **From PRFlow** β the Slack card on **Settings β Connections** has a
**Disconnect** button (admin-only, with a confirmation step). Your
repository and channel-mapping setup is kept, and notifications resume when
you reconnect. Disconnecting also revokes the bot token at Slack, so it
does not linger β unless another of your PRFlow organizations shares the
same Slack workspace, in which case revocation is safely skipped and only
your connection is removed.
* **From Slack** β uninstalling the PRFlow app from the workspace (or
revoking its tokens on Slack's side) disconnects **every** PRFlow
organization linked to that workspace immediately, deleting the stored
access tokens and stopping further posting attempts.
If Slack ever rejects a notification because a connection has gone bad,
PRFlow marks that connection unhealthy and stops trying to post through it
instead of failing on every event; reconnecting from the Connections page
restores it.
## Support
Questions about the integration, a channel that isn't showing up in the
picker, or a notification that didn't arrive? Email
[hello@prflow.dev](mailto:hello@prflow.dev) and we'll get you sorted.