The Remote Files API (RFAPI) provides partners with the ability to build file-centric apps that integrate deeply with Slack. Partners are able to store files in their systems while enabling people to easily share, access, and unfurl these files within the Slack client.
Using these APIs, your app can create, update, and share files within Slack. It can also listen for links to files and automatically unfurl a rich preview of those files.
This document will guide you through the requirements for building a fully featured Slack app that uses the Remote Files API.
bot
and user
tokens.bot
and user
events to capture as many unfurl events as possible.A remote file can also be thought of as a remote object. It is something that lives outside of Slack that you want to represent inside of Slack. Most of the time is satisfies these questions:
An example could be a text document being edited over a month or two before being published. Or a design file, that is being discussed and evolves with each round of feedback.
Remote files are added to Slack two ways, unfurling and sharing. Both of these will be walked through in detail below.
Whenever a remote file is added to Slack there are always two steps , First is adding of the file, which is independent from the share or unfurl. Then the message is sent to Slack referencing the file that was previously added.
Elements required for a remote file:
Item | Description | Required |
---|---|---|
token |
The bot token and user token required. You cannot use a user token to add a remote file and you cannot use a bot token to share or unfurl. |
Required |
external_url |
This will be the URL users are directed to when opening the file. | Required |
external_id |
The external_id will determine the uniqueness of a remote file. This can be any value that you have associated to the file on your end. Passing different ids for the same file when shared multiple times will result in having duplicate files. So you will want to ensure the same id is passed for a file each time. |
Required |
filetype |
The file type will be used by Slack to determine how to display the remote file. There are different UI treatments and icons for different file types. Because the file does not live with Slack, the file type auto is not supported. |
Required |
preview_image |
This will show in the conversation as the thumbnail image associated with the file. You can add remote files without a preview image but sending one is highly encouraged to ensure a rich user experience. |
Optional |
indexable_file_contents |
To help discoverability of the files that are shared in Slack. | Optional |
title |
Name of the file as you would like it displayed in Slack. | Required |
In addition to adding a file, you have the ability to update a file. This can be useful to keep the file contents up to date. It is up to you how often to update a file but at the very least it is recommended to update every time the file is shared in Slack.
To build unfurling into your app, you should add the following scopes and events:
Scopes | remote_files:read , remote_files:share , bot |
Events | link_shared (workspace event), link_shared (bot event) |
To capture as many unfurl events as possible is it necessary to subscribe to both the workspace and bot link_shared events.
For any domain for which you would like to receive unfurl events, you must register them in your app. Add the domains to the App Unfurl Domains section of the Slack application setup.
Steps to an unfurl:
Steps | Token |
---|---|
User pastes a URL that has a domain to which your app has subscribed. | N/A |
Slack emits the link_shared event. | N/A |
Your app checks if the user has authenticated. If the user needs to be authenticated then see Authentication below. | user token |
Call remote.files.add to add the remote file. Save the file_id or external_id . |
bot token |
Call chat.unfurl to share the remote file. Pass in the file block and any other block content for the message. |
user token* |
Slack will update the message showing the unfurl. | N/A |
*User token of user who installed the app or the unfurling user. Using the user token of the user that pasted the URL or the token of the user that installed the application, will both work. If you require each user to authenticate with your application then you should use the token of the user who pasted the link. If you do not require every user to authenticate then you can use the user token of the user who installed the application for all unfurls.
When a file is shared in Slack or to Slack, your application may have to check for Access Control.
When a file is shared into Slack you will have the decision on how much of the file contents to share to the conversation. If a file is public for anyone with the URL then, when possible, you should always send an image, a full preview, and indexable content.
If a file is only available to specific people then you will want to first check where the file is being shared. When a file is shared to a public Slack channel, it will be discoverable to all members of an organization.
Because the user took the action to share the file, this is an opportunity to help the user adjust file permissions. By calling conversations.members you can check the members of a channel against the access rights to the file.
If you find that certain users are not in the ACL then you can offer to fix this for the end user. This can reduce the number of times someone navigates to a file and then has to ask the owner for permission to view it.
Note: As a best practice only channels with less than ~50 users should be checked against access. This is to protect against lengthy operations when files are shared to huge public channels. In channels with more than ~50 people, access control should not be updated. In addition you should also not run an ACL fixer in a shared channel unless explicitly approved by the user
Requesting a bot token is a requirement for using the RFAPI, so there will have to be some bot interactivity in your app. Your app will have a presence on the team where it is installed. It does not mean you will have to name your bot and give it a persona.
There are a few ways in which a user may interact with your app. Your app will want to make sure all 3 of these use cases are handled because when installed, users will try to interact with your app.
Having timely and helpful app messages is great all around, but where they are really critical is for mobile use. If your app follows messaging best practices then it will become integral to a user’s mobile workflow.
Users expect to have search results be targeted and contain files that match their search. The RFAPI opens up this functionality for external apps. Files should be discoverable by title and contents for full functionality.
By utilizing the indexable content feature of the RFAPI, your app will be more discoverable, and users will be able to find, then navigate to external resources.
Make sure to send indexable_file_contents
to Slack when adding a file. What’s nice, is that users typically don’t search for a file within a couple of seconds of it being added to Slack, so there is some time for processing.
A common approach is to first add the file, then unfurl the message or share the file. Then afterwards, update the file with indexable_file_contents
. Slack will update this globally within seconds for search discoverability.
In the same way that a user should not have to leave Slack to complete a quick operation on your application, a user should not have to leave your application just to share something into Slack.
Allowing users to send Remote Files to Slack for review or awareness can be a great efficiency to provide. It allows your users to stay focused but let them send something along for discussion with their team.
Note: If the Slack application has not yet been installed or you want to send the message as the actual user. You will need to send the user through the Slack OAuth flow and save the information instead of users.lookupByEmail
.
To share a file into Slack, just like unfurling, the first step is to add the file via remote.files.add
.
After this you will want to send the file to Slack via chat.postMessage
. Using the file_id
returned in the first call, you will send a file block to Slack, along with context in additional blocks.
Payload:
[{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Quarterly Reports* \nCan I get some eyes :eyes: on this, almost finished."
}
},{
"type": "file",
"file_id": "FXXE6UE",
"source": "remote"
}]
In addition to text you could add other interesting block elements. For example, an approve or comment button.
To implement this functionality you will likely need a Slack logo, you can find the current brand guidelines here.
When installing a Slack app, users must always go through the Slack authentication flow. This will pass your app the information it needs to communicate with the Slack team.
Depending on the requirements on your side there are a couple of ways in which you can approach user authentication.
Send users through your own OAuth flow. Upon completion of the Slack OAuth flow, you can redirect users to your OAuth flow. This should be done if you need to ask for explicit approval for certain actions. Your app will then potentially have 3 tokens associated with the installing user. The bot token (common across all users on a team), Slack user token, your apps auth token.
Bypass your OAuth flow. It is common to launch an app using just the Slack OAuth flow. When your app is installed, the user will enter the Slack OAuth flow. The last step of this is a redirect back to your site. At this time you can either link the Slack account to your system if the user is signed in, or ask then to sign in to link the accounts.
When designing a Slack app it is a best practice to use the bot
token as much as possible. This reduces the need to ask additional members of a team to go through the Slack OAuth flow.
Unfurling is a time when you may need to send additional users through the OAuth flow. If you require additional auth on your end for the unfurl then you will have to send the user through the OAuth flow. Steps to accomplish this:
chat.unfurl
.If you would like to unfurl the message without authenticating the user then that also can be accomplished. Here are the steps. Unfurling without authenticating is a steam-lined way for the user to achieve the same results as regular unfurling.
link_shared
event. Slack signs its requests using a secret that's unique to your app.users.lookupByEmail
to associate the Slack user to a user you recognize. To do this you will need the users:read.email
scope.chat.unfurl
.Unfurling in shared channels should be disabled by default. To unfurl in shared channels is it suggested that you have an admin explicitly enable this feature.
In your datastore you should plan to associate the following information.
Value | Description |
---|---|
Enterprise Id | One per Enterprise Grid. One enterprise id could be associated to hundreds of team ids. |
Team Id | Unique id for a team within Slack. |
User Token | You may or may not have a token for each user. |
Bot Token | Same for all users in a workspace. Unique to a workspace. There can be multiple teams in an enterprise so you will have multiple bot tokens for one enterprise. |
Slack User Id | Slack represents users with a user id unique to a team. |
Team Id + Slack User Id (unique key) | This will differ depending on how you actually store the data. This level of uniqueness is important to call out for supporting Enterprise Grid. |
Email or your User Identifier | How you link the Slack user to a user in your ecosystem. |
App OAuth Token | If there is a token from your OAuth flow. |
If you have an administrator or owner defined in your app then it can be nice to give them additional control over the integration. Here are some things to think about:
If your app is uninstalled on a team then you will want to listen for the app_uninstalled
event. This is particularly important if you are utilizing the send to Slack functionality.
If you don’t listen for this event then you may receive an invalid_auth
error. If this happens, depending on what api call you are making, you can send the user back through the Slack OAuth flow.
Remote file unfurls live on after your application is uninstalled. This will not remove past file unfurls or shares. If your application is re-installed then you will have full access to the remote files already on the team as if your app was never uninstalled.
More and more Slack customers are moving to Enterprise Grid. This allows one organization to have multiple teams (sometimes hundreds) all associated.
Because of this you will run into things like shared channels and shared app DMs across teams. Details on supporting Enterprise Grid can be found here.
One specific thing to call out that is particularly important to apps utilizing remote files, is that you will have to plan for one to many relationships. One user or account in your app can be associated to many different Slack teams. It is important architecturally to plan for this because each team will have different tokens for interacting with Slack.
Users have Slack open on average 10 hours a day. This provides a unique opportunity to grab their attention when needed. This also provides the opportunity to distract users so notifications take quite a bit of thought to get right.
Here are some examples of things to think about notifying people in Slack about.
There are many more examples but these should get the creative juices flowing. When planning a notification strategy it is helpful to take each potential notification and compare it to these app guidelines. This also holds true for Slack actions that create notifications in your app.
It is useful to provide Slack with expectations about how you will be using the API. We would love to know:
Your app will need to store additional information to support its specific functionality. But to support a Slack app, here is an example of how you might go about storing data.
"user": {
"type": "object",
"required": true,
"description": "Contains data related to the user information that is being stored",
"enterprise_id": {
"type": "string",
"required": false,
"description": "The enterprise id for an Enterprise Grid team. Every team within one organization will share the same enterprise id."
},
"admin_installer": {
"type": "boolean",
"required": true,
"description": "When the application is first installed on a team, there is one individual who performed the installation. It is important to understand who is the administrator for a team."
},
"team_id": {
"type": "string",
"required": true,
"description": "The team id is unique across all of the Slack network. There can be multiple teams associated to one enterprise."
},
"user_token": {
"type": "string",
"required": false,
"description": "When a user goes through the OAuth flow, Slack will generate a token for the user. This token can be used to perform actions within Slack. Most actions can be performed with only the bot token, but there are times when the user token may be required."
},
"slack_user_id": {
"type": "string",
"required": true,
"description": "Each user within an organization will have a unique id assigned by Slack."
},
"user_id_team_id": {
"type": "string",
"required": true,
"description": "This key is created by combining the user id and the team id of the user. This should be used as the unique key for the record. It is important to have this level of uniqueness when dealing with Enterprise Grid or users using your app on multiple teams."
},
"unique_partner_user_identifier": {
"type": "string",
"required": false,
"description": "It is important to be able to tie a Slack user to a user within your system. This can be email if needed but ideally it would be some other identifier unique within your systems."
},
"partner_auth_token": {
"type": "string",
"required": false,
"description": "If your application requires a token to communicate with your systems then it should be associated with the Slack user. This is typically created during a double OAuth flow when a user first goes through the Slack OAuth flow and then an additional one for your systems."
},
"user_scopes": {
"type": "array",
"required": true,
"description": "When a user goes through the Slack OAuth flow they give permission to certain scopes. Slack scopes are additive so it is important to capture what access the user has allowed."
},
"events": {
"type": "array",
"required": true,
"description": "At the time of authentication, what event subscriptions were configured by your application. It is important to capture this if you plan on adding additional events in the future."
},
"last_message": {
"type": "object",
"required": false,
"description": "Captures the most recent message sent to the user. This can be useful for building interactivity. For more complex scenarios you may need to store more than one message per user.",
"last_message_time": {
"type": "string",
"required": true,
"description": "The time stamp of the last message sent to the user."
},
"last_message_slack_time_stamp": {
"type": "number",
"required": false,
"description": "The Slack timestamp associated with the message."
},
"unfurl": {
"type": "boolean",
"required": true,
"description": "Whether the message was part of an unfurl event."
},
"authed": {
"type": "boolean",
"required": true,
"description": "Had user gone through the OAuth flow when the last message was sent."
},
"message_id": {
"type": "string",
"required": true,
"description": "Generated by your systems. Important to store for interactivity in messaging."
},
"dm": {
"type": "boolean",
"required": true,
"description": "Whether the last message was sent in a direct message with the Slack application."
}
},
"preferences": {
"type": "object",
"required": false,
"description": "Your application can maintain a set of user preferences.",
"unfurl": {
"type": "boolean",
"required": false,
"description": "Determines whether the application should respond to link_shared events."
},
"send_to_slack": {
"type": "boolean",
"required": false,
"description": "In the same way a user should not have to leave Slack to complete a quick operation on your application, a user should not have to leave your application just to share something into Slack. This preference gives the user the opportunity to opt out of this behavior."
},
"notification_level": {
"type": "number",
"required": false,
"description": "Your application can be give a user the ability to select how often or what types of events they would like to be notified about."
}
}
}
"team": {
"type": "object",
"required": true,
"description": "Applications are always installed at the team level. Even when there are multiple teams within an organization, each team will have its own installation.",
"team_id": {
"type": "string",
"required": true,
"description": "The team id is unique across all of the Slack network."
},
"enterprise_id": {
"type": "string",
"required": false,
"description": "The enterprise id is unique across all of the Slack network can relate to multiple teams."
},
"bot_token": {
"type": "string",
"required": true,
"description": "There is one bot token for each team installation. User tokens are unique to each user but a bot token covers the entire team."
},
"scopes": {
"type": "array",
"required": true,
"description": "When an installing user goes through the Slack OAuth flow when installing the application, they give permission to certain scopes for the bot. Slack scopes are additive so it is important to capture what access the bot has allowed."
},
"events": {
"type": "array",
"required": true,
"description": "At the time of installation, what event subscriptions were configured by your application. To subscribe to more events for a team, your application must be re-authenticated."
},
"admin_preferences": {
"type": "object",
"required": false,
"description": "In addition to user preferences you can give admins the ability to set team level preferences for the application.",
"send_full_pdf": {
"type": "boolean",
"required": false,
"description": "Determines whether to send full PDF previews of files sent to Slack."
},
"send_indexable_content": {
"type": "boolean",
"required": false,
"description": "Slack can make your files more discoverable by surfacing them in search. However some teams may not want to send content to Slack."
},
"unfurl_in_shared_channels": {
"type": "boolean",
"required": false,
"description": "Your application could receive link_shared events from messages in shared channels across different organizations. Admins should be able to select the behavior for these circumstances."
},
"send_to_slack": {
"type": "boolean",
"required": false,
"description": "In the same way a user should not have to leave Slack to complete a quick operation on your application, a user should not have to leave your application just to share something into Slack. This preference gives the admin the opportunity to opt out of this behavior for the team as a whole."
},
"notification_level": {
"type": "number",
"required": false,
"description": "Your application can be give a user the ability to select how often or what types of events they would like to be notified about."
}
}
}