Building with Chat Attachments

This guide is for Flex UI 1.x and channels that use Programmable Chat and Proxy. If you are using Flex UI 2.x or you are starting out, we recommend that you build with Flex Conversations.

Chat attachments allow agents and customers to send non-text content, like images, pdfs, and videos using Flex Webchat. While you can do a lot of cool stuff using chat attachments, you'll need to create rules and safeguards to keep agents safe, customers happy, and your business secure. Fortunately, chat attachments were built in a customizable way. In the following code samples, you can see some of the strategies the Twilio team has cooked up to implement some common scenarios.

There are many other ways to solve these problems, and your problems will likely be more complex than the scenarios presented here. These code samples are meant to be a starting point to help familiarize you with some of the tools you have available - so definitely tweak these to fit your preferred tooling and patterns!

Use Programmable Chat and the Flex UI to delete messages

Each chat attachment is associated with a Message in the Programmable Chat API. To delete a file which has been sent, you can therefore delete the message using the remove method provided by Programmable Chat SDK.

// Add a delete button. to every MessageListItem

const DeleteMessage = ({ message }) => (
   // message is the default prop passed through by the MessageListItem
   <button type="button" onClick={() => message.source.remove()}>

Flex.MessageListItem.Content.add(<DeleteMessage key="delete-message" />, { sortOrder: -1 });

Replace Actions to upload files to personal storage

By default, chat attachments uses Twilio Chat's Media Resource for file storage. To use your own storage for message attachments, you’ll need to replace the SendMediaMessage action, which is triggered when a media file is sent.

You’ll need to:

  1. Upload the file to our own private storage
  2. Pass on the unique identifier to the replaced sendMessage Action in messageAttributes
  3. Trigger a regular sendMessage action.
// Implement personal storage

const uploadFileToMyStorage = async (file) => {
   const formData = new FormData();
   formData.append("image", file);

   // Upload the file to private storage
   const res = await fetch("", {
       method: "POST",
       headers: new Headers({
           Authorization: "Client-ID 546c25a59c58ad7"
       body: formData
   return res.json();

// Replace the action
Flex.Actions.replaceAction("SendMediaMessage", async (payload: Flex.ActionPayload) => {
   const { file, messageAttributes, channelSid } = payload;

   // Retrieve the uploaded file location
   const res = await uploadFileToMyStorage(file);

   // Include the new media file when sending the message
   return Flex.Actions.invokeAction("SendMessage", {
       messageAttributes: {
           media: {
               contentType: file.type,
               size: file.size

// Now you need to render your uploaded file. First, delete the body of the MessageBubble. Then, add a new body, including appropriate HTML that points to your uploaded file (in this example, an image tag is sufficient for rendering the image. Don’t forget your alt text!)

// Create new message bubble content
const PersonalStorageContent = ({ message }) => (
       <img src={ alt=”file uploaded from custom storage” style={{ width: "100%" }} />

Flex.MessageBubble.Content.remove("body", {
   if: (props) => !!

Flex.MessageBubble.Content.add(<PersonalStorageContent key="message-bubble-body" />, {
   if: (props) => !!

Use before Action hooks to filter content and check for viruses

Depending on your use case, content filtering and virus checking can be done in different ways, but most implementations will involve the before action hooks. For example, you can use it to download the file after content filtering, or run the downloaded file against a set of guidelines before it gets sent as a message.

// Check file content

Flex.Actions.addListener("beforeDownloadMedia", async (payload, cancelAction) => {

   const { message } = payload;

   const url = await;

   // Validate file before download (note that checkFileContent method needs to be written)

   const result = await checkFileContent(url);

   if (!result.pass) {

       // Failed to validate content of the file




Flex.Actions.addListener("beforeSendMediaMessage", async (payload, cancelAction) => {
   const { file } = payload;

   // Validate file before sending
   const result = await checkFileContent(file);

   if (!result.pass) {
       // Failed to validate content of the file

Flex.Actions.addListener("beforeAttachFile", async (payload, cancelAction) => {

   const { file } = payload;

   // Validate file before attaching
   const result = await checkFileContent(file);

   if (!result.pass) {
       // Failed to validate content of the file

Use Programmable Chat to change permissions of inappropriate users

Sometimes a chat user can send inappropriate files or messages and as an agent you might want to block this user from sending messages or media messages. This can be done using programmable chat rest api.

All users in chat are associated with a Role and Permissions. To block users, you'll want to create a blockedUser Role. Based on your needs, remove the sendMessage and/or sendMediaMessage permissions from the new Role

In order to block somebody, you can update the Role of their Member Resource using a Twilio Function. Provide the new blockedUser SID as the roleSid parameter. Check out the guide on using Functions from your Plugin for additional support.

Twilio Function Code

exports.handler = function(context, event, callback) {
    // Use context parameter to initialize Twilio client and retrieve stored environment variables
	const twilioClient = context.getTwilioClient();
	const chatServiceSid = context.CHAT_SERVICE_SID; // // Get Chat service sid from
	const blockedUserSid = context.BLOCKED_USER_SID

    // Use the event parameter to retrieve dynamic information, like the current chat channel and the member to blockedUserSid
    const {chatChannelSid, memberSid} = event
       .update({roleSid: blockedUserSid})
       .then(member => callback(null, member.sid))
       .catch(err => callback(err, null))

Plugin Code

// Create function to block user
const blockUser = (chatChannelSid, memberSid) => {
    const body = { chatChannelSid, memberSid };

    // Set up the HTTP options for your request
    const options = {
      method: 'POST',
      body: new URLSearchParams(body),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'

    // Make the network request using the Fetch API
    fetch('', options)
      .then(resp => resp.json())
      .then(data => console.log(data));

// Create a button component to block users
const BlockUserButton = (props) => (
   <button type="button" onClick={() => blockUser(props.channelSid, props.member.source.sid)}>

// Insert Block User Button into the Flex UI
Flex.MessageBubble.Content.add(<BlockUserButton key="block-user" />, {
   if: (props) => !props.message.isFromMe
Rate this page:

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd by visiting Twilio's Stack Overflow Collective or browsing the Twilio tag on Stack Overflow.

Thank you for your feedback!

Please select the reason(s) for your feedback. The additional information you provide helps us improve our documentation:

Sending your feedback...
🎉 Thank you for your feedback!
Something went wrong. Please try again.

Thanks for your feedback!