Modal in Slack are pop up window that appear in Slack where you can ask a user specific questions to get information that your app can use. Just to start off with
For a test workflow to see if I can get this to work correctly will look like this
- The user will post in a channel
- The bot will respond to the user as a reply with two buttons, report an error, or request a new feature
- Depending on which button is pressed, a modal will appear asking the user for more information.
- The bot will reply to the thread with the user response

Block Kit Builder
The easiest way to start creating interactive element in Slack is to use Block Kit builder. Block kit build show you all the interactive building block that you can use. It will show what it will look like on Slack on the left, and the actual JSON code on the right.

To use these block in a Python Lambda function, copy the JSON and add it as a new triple quote string in your python file. For example
How to Get a modal to appear in Slack
Modal can appear if a user does a / command or a user push a bottom. I’m going with the button approach. One button to report an issue one to request a new feature. If we want to use a button, we’ll need to use block kit for the response text and not plain text. I also want to the bot reply to be a thread in slack and not a new message. So we’ll need to update the send_text_response in the first blog post to handle this.
def send_text_response(blocks, channel_id=None, thread_ts=None, user=None):
# use postMessage if we want visible for everybody
# use postEphemeral if we want just the user to see
slack_url = "https://slack.com/api/chat.postMessage"
channel_id = channel_id
user = user
bot_token = os.environ["BOT_TOKEN"]
thread_ts = thread_ts
data = urllib.parse.urlencode({
"token": bot_token,
"channel": channel_id,
"blocks": blocks,
"user": user,
"link_names": True,
"parse": True,
"thread_ts": thread_ts
})
data = data.encode("utf-8")
print(data)
request = urllib.request.Request(slack_url, data=data, method="POST")
request.add_header("Content-Type", "application/x-www-form-urlencoded")
res = urllib.request.urlopen(request).read()
print('res:', res)
The function is similar, with thread_ts added. This is the time stamp of the message we are creating a thread under. Blocks is the block kit with the 2 button we can the user to push to start the modal. You can see the block hit here.
How to get the modal to appeal when the button is pushed
When a user push a button an event will be fired off to AWS, this event will be of type ‘block_actions’. So we’ll want to update our lambda_handler to handle the block_actions differently than a message event.
def lambda_handler(event, context):
if event["isBase64Encoded"]:
event = covert_base_64(event)
else:
event = json.loads(event["body"])
event = event['event']
print('event', event)
if event['type'] == 'message':
print('message')
parse_message(event)
return {
'statusCode': 200,
'body': 'OK'
}
if event['type'] == 'block_actions':
print('block_actions')
parse_button_push(event)
return {
'statusCode': 200,
'body': 'OK'
}
def parse_button_push(event):
trigger_id = event['trigger_id']
channel_id = event['container']['channel_id']
thread_ts = event['container']['thread_ts']
if event['actions'][0]['action_id'] == 'help_me':
modal = update_modal(channel_id, thread_ts, BLOCK_MODAL)
if event['actions'][0]['action_id'] == 'new_module':
modal = update_modal(channel_id, thread_ts, NEW_MODULE_MODAL)
send_modal(trigger_id, modal)
def send_modal(trigger_id, modal):
slack_url = 'https://slack.com/api/views.open'
bot_token = os.environ["BOT_TOKEN"]
data = urllib.parse.urlencode({
"token": bot_token,
"trigger_id": trigger_id,
"view": modal
})
data = data.encode("utf-8")
print(data)
request = urllib.request.Request(slack_url, data=data, method="POST")
request.add_header("Content-Type", "application/x-www-form-urlencoded")
res = urllib.request.urlopen(request).read()
print('res:', res)
The code is pretty simple. In parse_button_push each of the bottom in slack we can give a specific action_id. By using the action id we can tell which button was pushed and which modal block kit string to send back to the user. Now because i’m not using a database my app doesn’t know what the channel_id or thread_ts that it will need to reply to when the user hit submit. For now i’m cheating and putting them as text fields at the bottom of the Modal. In the future adding this to a database would be the way to go

How to handle a modal submit
When a user hits submit on the modal, a new event is sent to AWS. This new event is of the type view_submission. It will include all the information from the modal. I recommend reading Slack Modal Lifecycle as there are some rules you have to follow that are not the same for text response.
def lambda_handler(event, context):
if event["isBase64Encoded"]:
event = covert_base_64(event)
else:
event = json.loads(event["body"])
event = event['event']
print('event', event)
if event['type'] == 'message':
print('message')
parse_message(event)
return {
'statusCode': 200,
'body': 'OK'
}
if event['type'] == 'block_actions':
print('block_actions')
parse_button_push(event)
return {
'statusCode': 200,
'body': 'OK'
}
if event['type'] == 'view_submission':
print('view_submission')
parse_modal_submit(event)
return {
"response_action": "clear"
}
def parse_modal_submit(event):
message, thread_ts, channel_id, return_text = parse_responce(event)
return_block = create_responce_message(message, return_text)
send_text_response(return_block, channel_id, thread_ts)
def parse_responce(event):
message = {}
if event['view']['blocks'][0]['text']['text'] == "Ansible_bug_report":
for block in event['view']['state']['values']:
for key in event['view']['state']['values'][block]:
message[key] = event['view']['state']['values'][block][key]['value']
return_text = RETURN_TEXT
if event['view']['blocks'][0]['text']['text'] == "Ansible_new_feature":
for block in event['view']['state']['values']:
for key in event['view']['state']['values'][block]:
message[key] = event['view']['state']['values'][block][key]['value']
return_text = RETURN_TEXT_NEW_FEATURE
thread_ts = (event['view']['blocks'][-1]['text']['text']).split(':')[1]
channel_id = (event['view']['blocks'][-2]['text']['text']).split(':')[1]
print(message)
return message, thread_ts, channel_id, return_text
The first thing we are going to do is parse the message Slack returned us. This return JSON will contain the responseThe first thing we are going to do is parse the message Slack returned us. This return JSON will contain the response to the value we want, but they’re rather deep in there. Something like this will get us the key (these were sent in the modal block message to id each question), and the value the user put .
for block in event['view']['state']['values']:
for key in event['view']['state']['values'][block]:
message[key] = event['view']['state']['values'][block][key]
After this we put the values in to the create_responce_message function that will respond to the thread with the aNow when the user hits submit, they will get a reply in the thread with their answers. Not that useful, but it means the modal was able to send data to AWS, and AWS was able to respond. Next steps would be to add this to a database
Leave a comment