Passing metadata to actions, sending messages as users and deleting ephemeral messages in Slack Bolt JS SDK

Passing metadata to actions, sending messages as users and deleting ephemeral messages in Slack Bolt JS SDK
Photo by Scott Webb / Unsplash

The Slack documentation is severely lacking when explaining certain things. In this doc, I'm going to explain certain problems I tried to solve and how you can solve them too.

Passing Metadata to Actions

If you try to Google "Passing Metadata to Actions" while working with actions which need to react to some input, you might come across this documentation page - Using message metadata. However, you have to throw away everything you read in that page as it's completely different in the lived reality of using the Bolt SDK.

Let's say you have a registered slash command /testcommand and you have some actions attached to it.

app.command("/testcommand", async ({ command, ack, respond, body }) => {
  await ack();
  const result = doSomething(command.text);

  await respond({
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: result.text,
        },
      },
      {
        type: "divider",
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: { type: "plain_text", text: "Send" },
            action_id: "test_send"
          },
          {
            type: "button",
            text: { type: "plain_text", text: "Cancel" },
            action_id: "test_cancel"
          },
        ],
      },
    ],
  });
});

As a part of this command, you run a function doSomething on user input and try to pass it to an action as a value. If you pass this as a string, it should be fine, but what if you're trying to pass in something more complex, such as a JSON object? In that case, JSON.stringify comes to the rescue!

Stringify the JSON on the value property, which gives you a JSON string at the receiving end of the action. In the case above, I want the result of the method to be sent to the test_send action, and the message body be sent to test_cancel so I can action on it there.

Here's how I would do it in the actions block:

{
  type: "actions",
  elements: [
    {
      type: "button",
      text: { type: "plain_text", text: "Send" },
      action_id: "test_send",
      value: JSON.stringify({ result }),
    },
    {
      type: "button",
      text: { type: "plain_text", text: "Cancel" },
      action_id: "test_cancel",
      value: JSON.stringify({ message: body.message }),
    },
  ],
},

And on the actual action, use JSON.parse to get the original value back ;)

app.action("test_send", async ({ action, ack, respond }) => {
  await ack();
  const metadata = JSON.parse(action.value); // Parse metadata from value

  console.log({ metadata });

  await respond({
    text: metadata.result.text,
  });
});

Delete Ephemeral Messages

In case you looked up deleting ephemeral messages on the Bolt JS SDK docs, you might have ended up somewhere around here - Using respond(). What it doesn't say though is how you actually delete older ephemeral messages, in case you don't want to pollute a user's feed with one-off messages.

This one is easy-peasy, just respond with a new message while setting the text to a blank string "" and delete_original to true.

app.action("test_cancel", async ({ ack, respond }) => {
  await ack();
  await respond({
    text: "",
    delete_original: true,
  });
});

Sending Message as User

The online documentation doesn't explain this, but you can send messages as user (impersonation) by either using say, which uses chat.postMessage underneath, or by using client.chat.postMessage directly.

In case of chat.postMessage, the only extra parameter you have to pass is channel which contains the channel ID. Make sure type completions are turned on in your project so you get all of the required suggestions by your IDE itself.

app.action("translate_send", async (opts) => {
console.log({ opts });
const { action, ack, body, client, say } = opts;
await ack();
const channelId = body.channel?.id;

    const userData = await client.users.profile.get({
      user: body.user.id,
    });

    await say({
      text: 'Hello World',
      username: userData.profile?.real_name,
      icon_url: userData.profile?.image_192,
    });

    await client.chat.postMessage({
      channel: channelId,
      text: 'Hello World',
      username: body.user.username,
      icon_url: userData.profile?.image_192,
    });

});