Open-source project
jenkinsci/slack-plugin avatar
jenkinsci/slack-plugin

Jenkins Slack plugin: notifications, threads and file uploads from a pipeline

A Jenkins plugin for posting notifications to a Slack channel

672 stars394 forksJavaMIT

At a glance

What is it?
The Jenkins Slack plugin posts build notifications to Slack and to Slack-compatible servers such as RocketChat and Mattermost, and its pipeline step is far more capable than a plain message: it returns the channel and thread identifiers so you can reply, update a message later, upload a file into a thread, and add a reaction. The setup burden is a Slack app manifest and a bot token, and the plugin itself is MIT licensed.
Who is it for?
Adopt the Jenkins Slack plugin if your Jenkins jobs need to talk to Slack, especially if you want threaded build conversations, message updates that reflect a changing build state, or file uploads attached to a notification rather than a wall of separate messages.
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 last received commits 5 days ago.
What is it written in?
Mainly Java, 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

Installing the plugin and creating the Slack app

The plugin provides Jenkins notification integration with Slack or with Slack compatible applications like RocketChat and Mattermost, and it is distributed through the Jenkins plugin manager. The install path is the standard one: from the Jenkins homepage navigate to Manage Jenkins, then Manage Plugins, change the tab to Available, search for slack, and check the box next to install.

The second half of the setup is creating a Slack app, and this is where the plugin's documentation is most specific. You go to the Slack apps page, click Create New App, choose From an app manifest, select your workspace, delete the example manifest that Slack provides, click YAML, and paste in a manifest like this:

yaml
oauth_config:
  scopes:
    bot:
      - channels:read
      - chat:write
      - chat:write.customize
      - files:write
      - reactions:write
      - users:read
      - users:read.email
      - groups:read

Then you click Next, Create, Install App to Workspace and Allow, and go to OAuth and Permissions in the sidebar to copy the Bot User OAuth Access Token. On the Jenkins side, the token goes into a Secret text credential under Manage Jenkins, System, created with the Add button and then selected in the dropdown. You also add a default channel, which the documentation says can be removed after you have validated that the connection works, and you tick the Custom slack app bot user option.

The scopes list is worth reading before you paste it. chat:write is the obvious one. chat:write.customize is what allows the formatted messages the plugin supports. files:write is what backs the upload step. reactions:write backs the reaction feature. And users:read, users:read.email and groups:read are the ones that let the plugin look up people, which is how a notification can name a user rather than a channel. If your Slack workspace restricts app permissions, those are the scopes that get queried.

The last step of setup is the one people forget: invite the Jenkins bot user into the Slack channels you want notified in. Until that is done the plugin has a valid token and still cannot post, because a bot cannot join a channel by itself.

A pipeline step that returns the identifiers you need

The simplest use of the plugin in a pipeline job is a single line:

slackSend color: "good", message: "Message from Jenkins Pipeline"

The step returns an object, and the reason that matters is that the object carries the channel ID and the thread ID of what it just posted. That turns a one-way notification into something you can build on.

The thread example is the clearest case. You send a primary message, then send further messages targeting the thread ID returned by the first call, and all messages of a thread should use the same thread ID:

groovy
def slackResponse = slackSend(channel: "cool-threads", message: "Here is the primary message")
slackSend(channel: slackResponse.threadId, message: "Thread reply #1")
slackSend(channel: slackResponse.threadId, message: "Thread reply #2")

The same identifier also enables two other behaviours. Messages posted to a thread can optionally be broadcast to the channel, which you do with replyBroadcast: true, and that is how a pipeline announces a failure in a thread without leaving the main channel silent:

groovy
def slackResponse = slackSend(channel: "ci", message: "Started build")
slackSend(channel: slackResponse.threadId, message: "Build still in progress")
slackSend(
    channel: slackResponse.threadId,
    replyBroadcast: true,
    message: "Build failed. Broadcast to channel for better visibility."
)

The update feature follows from the same idea. The step returns a timestamp and a channel ID, and the note explains why you need the channel ID as well: the Slack API requires the channel ID for chat.update calls. You post once, then post again with the same channel and the timestamp to replace the content:

groovy
def slackResponse = slackSend(channel: "updating-stuff", message: "Here is the primary message")
slackSend(channel: slackResponse.channelId, message: "Update message now", timestamp: slackResponse.ts)

That is a genuine advantage over a webhook-based notifier, which can only append. A build that posts at start, updates itself while it runs, and updates itself again at the end occupies one message in the channel.

Attachments, blocks, and the legacy warning that comes with them

Beyond plain text, the plugin can send Slack attachments and Block Kit blocks, and the README links the Slack Messages API, the Slack attachments API and Block Kit for the details.

An attachment is a list of dictionaries passed straight through to the API:

groovy
def attachments = [
  [
    text: 'I find your lack of faith disturbing!',
    fallback: 'Hey, Vader seems to be mad at you.',
    color: '#ff0000'
  ]
]

slackSend(channel: "#general", attachments: attachments)

Blocks are the newer and richer format, a list of typed blocks such as section and divider, each with its own payload:

groovy
blocks = [
	[
		"type": "section",
		"text": [
			"type": "mrkdwn",
			"text": "Hello, Assistant to the Regional Manager Dwight! *Michael Scott* wants to know where you'd like to take the Paper Company investors to dinner tonight.\n\n *Please select a restaurant:*"
		]
	],
]

slackSend(channel: "#general", blocks: blocks)

The important caveat is stated right after the examples: the attachments API is classified as legacy. It does not support newer features such as blocks, file uploads, or threads. So the two formats are not interchangeable, and if you want threads, file uploads or a rich layout you have to commit to blocks.

That is a real constraint on the design of a pipeline notification. The older attachment format gives you a coloured bar and a fallback text and is easy to produce. The block format gives you layout, interactive elements and threads, and it is the one Slack is still developing. The plugin supports both, and the choice is yours, but mixing them in the same pipeline usually means maintaining two message-building paths.

File uploads, including into a thread

The upload step is separate from the send step and is called slackUploadFile. The basic form runs inside a node block, because it needs a workspace to write the file into first:

groovy
node {
  sh "echo hey > blah.txt"
  slackUploadFile filePath: "*.txt", initialComment:  "HEY HEY"
}

The filePath argument is a glob, so a single call can pick up several files from the workspace, which is what you want for build artifacts.

Uploading to a user channel works differently, and the mechanism is worth understanding. You upload to a user channel by messaging the user first and then using the channel ID from the message response:

groovy
node {
  sh "echo hey > blah.txt"
  def slackResponse = slackSend channel: '$userId', message: 'Hey', sendAsText: true
  slackUploadFile filePath: "*.txt", channel: slackResponse.channelId
}

So the plugin has no concept of a direct message channel. It gets one by opening a conversation, and a conversation with a person has a channel ID that a bot can then upload into.

For threads, the syntax is a channel and a timestamp joined by a colon:

groovy
def slackResponse = slackSend(channel: "cool-threads", message: "Here is the primary message")
sh "echo hey > blah.txt"
slackUploadFile(channel: "cool-threads:" + slackResponse.ts, filePath: "*.txt", initialComment:  "A file, inside a thread.")

The combination of channel and timestamp is what Slack expects for a threaded upload, and the plugin takes it as a single string, so you concatenate them yourself.

Reactions, and where the plugin is heading

The last documented feature is emoji reactions on a previously sent message, which uses the same return-object pattern as the thread support:

groovy
def slackResponse = slackSend(channel: "emoji-demo", message: "Here is the primary message")
slackResponse.addReaction("thumbsup")

The reaction is added to the message that the response refers to, so the mechanism is the same as the thread ID: the plugin hands you an object that is bound to what it just did, and you use that to act on it later. A pipeline that acknowledges a deploy with a thumbs up, or marks a flaky job, is a small script on top of that.

One scope detail is worth knowing here, because reactions on a message someone else posted need the reactions:read permission, and the manifest in the README grants reactions:write. If you find that a reaction on your own message works and one on someone else's does not, that gap is the explanation.

The plugin is MIT licensed, and the repository layout shows the standard Jenkins plugin structure: a pom.xml, a src/ directory, a Jenkinsfile for the build, a checkstyle.xml, a .mvn/ directory, a CHANGELOG-old.md and a docs/ directory. The Jenkins plugin site is the homepage, and the topic list carries a hacktoberfest tag, which means the project has taken outside contributions through that programme.

The recent releases are 795.v4b_9705b_e6d47 from 2025-08-04, 794.va_5081e5ea_423 from 2025-07-31 and 761.v2a_8770f0d169 from 2025-01-24, and the last push was on 2026-10-19, so the code is being maintained even though the newest published plugin is a little over a year old.

Global notifications, and what the plugin does not do

There is one important scope decision the README points you at rather than solving. If you want to configure a notification to be sent to Slack for all jobs, you may want to also consider installing an additional plugin called the Global Slack Notifier plugin.

That is a deliberate split. The plugin you are looking at is a pipeline step, so it is called from a job that has decided to notify. A global notifier is a different mechanism, driven by Jenkins events rather than by a script, and it lives in a separate plugin so that each stays focused. Trying to configure this plugin to fire on every job would mean either duplicating the step in every pipeline or building a shared library, and the shared library is a step further than a global notifier.

The other thing the plugin does not do is authenticate for you. There is no managed Slack service, no Slack-hosted gateway. You create an app, you grant it scopes, you paste a token into a Jenkins credential, and you maintain the connection. For a team that already has a Slack app and a Jenkins instance, that is entirely reasonable, and the app manifest in the documentation makes it a twenty-minute job rather than a project. For an organisation that would rather not run a bot in its chat workspace, it is a non-starter.

There is also a security consideration worth a sentence of its own. The bot token is a real credential, and the plugin's own documentation puts it in a Secret text credential rather than in plain configuration, which is the right call. What the manifest scopes determine is how much that credential can do. The manifest in the README asks for users:read.email, which grants access to email addresses of workspace members, and a deployment that does not need to name people in notifications should drop that scope before installing.

Editorial conclusion

Adopt the Jenkins Slack plugin if your Jenkins jobs need to talk to Slack, especially if you want threaded build conversations, message updates that reflect a changing build state, or file uploads attached to a notification rather than a wall of separate messages. Do not adopt it if you expect a managed bot service that needs no credentials of your own, because the whole flow starts with you creating a Slack app and pasting a bot token into Jenkins, and a bot has to be invited to each channel you want it in. Before you commit, read the OAuth scopes in the manifest and remove the ones your channels do not need, add the bot to the target channels yourself, and test the connection from Manage Jenkins, since a missing channel invitation looks the same as a broken token.

Frequently asked questions

Which chat platforms does the Jenkins Slack plugin support?

It provides Jenkins notification integration with Slack and with Slack compatible applications like RocketChat and Mattermost. The plugin is published on the Jenkins plugin site as slack and can be installed from Manage Jenkins, Manage Plugins, the Available tab.

How do I send a threaded message from a Jenkins pipeline?

The slackSend step returns an object carrying the thread ID, and you send further messages with that ID as the target channel. All messages of a thread should use the same thread ID, and a threaded message can be broadcast to the channel by setting replyBroadcast: true.

Can the Jenkins Slack plugin upload files?

Yes, through the slackUploadFile step, with a filePath glob and an initialComment, and it can upload to a thread by joining the channel and the timestamp with a colon. To upload to a user channel you message the user first and reuse the channel ID from the response.

Can a Jenkins notification be updated after it is posted?

Yes. The step returns a timestamp and a channel ID, and the note explains that the Slack API requires the channel ID for chat.update calls, so you post again with the same channel and the timestamp to replace the message content.

Do I need a separate plugin to notify Slack for every job?

The README suggests also considering the Global Slack Notifier plugin if you want a notification sent to Slack for all jobs, since this plugin provides the pipeline step that a job calls explicitly. The two are separate plugins with different mechanisms.

Official sources

  1. jenkinsci/slack-plugin on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jenkinsci-slack-plugin.svg)](https://hysenlabs.com/projects/jenkinsci-slack-plugin)

Was this useful?