This feature has not been released publicly, and we politely request that you treat everything in this document as confidential.

All details outlined in this document are in beta. We may change the timeline, APIs, and functionality based on feedback from pilot testers like yourself, so you should be prepared to update your code accordingly.

Remote Files App Guidelines

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.

Key Takeaways

  • Using the RFAPI requires the use of both bot and user tokens.
  • Most likely you will need to have users, in addition to the installing user, auth against your app. Take advantage of ephemeral messages and the DM space with your bot to accomplish this.
  • When you subscribe to a domain to take over unfurling of URLs, Slack will not longer unfurl these URLs, even if you don’t respond to the event. You must subscribe to both bot and user events to capture as many unfurl events as possible.
  • Enterprise Grid and shared channels are a growing part of Slack. You should plan to support these features as your app will encounter them.
  • Plan on sending thumbnails and indexable content to build a great app.

What is a File?

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:

  1. Is this dynamic? Will it change over time?
  2. When a user in Slack searches for it, would it be appropriate to show with the file filter applied?
  3. Is this object collaborative? Would it benefit from multiple people contributing to it?

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 API Usage

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.

File Unfurling

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.

File Access Control

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

Bot interactivity

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.

  1. If a user @mentions your bot user. Make sure to subscribe to the app_mention event to listen for instances when a user is trying to get in touch with your app or is referring to it in a message.
  2. When a user navigates to the app DM. When a user clicks on your app in the sidebar, Slack will send an event to let you know this has happened if you subscribe to the app_home_opened event. When you receive this event, you have the ability to message the user with on-boarding steps, authentication, or other useful information. Great blog post on this topic. For example if a user opens the app home you could send list of recent documents for them to share in Slack or open on your site.
  3. When your app initiates a message ephemerally. The most common case for an app to send an ephemeral message would be to ask for authentication when unfurling a document. Access control messages would also be sent ephemerally.

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.

Sending to Slack

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.

Authentication

Install

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.

  1. 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.

  2. 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.

Unfurl

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:

  1. Send the user an ephemeral message. This message should give the user the option to unfurl and auth against your app and also to ignore. It is rare but some users do not want their links to unfurl so it is important to give them that option.
  2. The user will click the button in the ephemeral message to auth. They will first go through the Slack OAuth process and then be redirected to your site.
  3. Once authenticated the user will head back to Slack where the link will unfurl after your app calls 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.

  1. Your application has received the link_shared event. Slack signs its requests using a secret that's unique to your app.
  2. If you do not recognize the user then you can call users.lookupByEmail to associate the Slack user to a user you recognize. To do this you will need the users:read.email scope.
  3. Use the user token saved from the OAuth flow during app installation to call 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.

Store Authentication Information

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.

Admin controls

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:

  • What types of files to share? Are there some file types you want to avoid sharing in Slack?
  • When you encounter externally shared channels what behavior should the app take? Should you unfurl the file details or not?
  • Notifications. Every team works different so giving granular control of what notifications should be sent and when can be very helpful.

Dealing with Uninstalls

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.

Enterprise Grid Support

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.

Notifications

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.

  • A user @mentions another user with a message about a file.
  • There has been an update to the file.
  • A file has been shared to a channel.
  • A file has been shared in a direct message.
  • The file has changed state, ex: moved from pending to approved.
  • A new file was created.
  • A task is overdue.

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.

  1. Concision of Information. To what extent does the app provide information at the right level of detail in user messages?
  2. Fluency of Visual Processing. To what extent does the app structure make notifications understandable?
  3. Proximity of Action. To what extent does the app allow users to complete routine or key actions without leaving Slack?
  4. Attention Conservation. To what extent does the app allow users to configure when and how they are contacted?
  5. Guidance and Recovery. To what extent does the app offer guardrails to avoid error, and what solutions does it offer for the user to recover?

Additional Reading

App Pre-Launch Checklist

  • Correct scopes have been requested.
  • The app is subscribed to the events needed to power the interaction.
  • @mention-ing the bot user provides a response.
  • Typing help in the app DM triggers a message from your app.
  • When a user opens a DM with your app, the app proactively responds with a message.
  • Install auth and additional user auth both work.
  • Send to Slack shows as expected.
  • Unfurling works in public and private channels.
  • Enterprise Grid is supported.
  • Shared channels are supported.
  • Multiple Slack teams and one user account functions as expected.
  • Multiple user accounts, one Slack team functions as expected.
  • When a file is unfurled or sent to Slack it shows up as a file in search.
  • The correct filetypes are being sent and the icons looks correct in Slack.
  • When a file is updated, the updates show in Slack.

What to Provide to Slack

It is useful to provide Slack with expectations about how you will be using the API. We would love to know:

  • Expected number of files sent per week.
  • What features are you planning to use.
  • What data elements will you be sending to the API.
  • How you will be updating files.
  • Typical image sizes and dimensions that will be sent to Slack.
  • If possible, a sample of how and what you will be storing for data (eg. tokens, user ids, etc…).

Sample Data Model

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 Schema

    "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 Schema

    "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."
            }
        }
    }
Python is now Workflow Builder's perfect pair
Learn how to use our Bolt for Python SDK to build functions for your team.