Synchronizing Data At last, we’ve arrived at the final part of our series on using Salesforce’s to quickly scaffold a deployable Slack App that interacts with Salesforce data. The Slack Starter Kit makes it tremendously easy to authenticate with Salesforce, organize your code into reusable chunks, and deploy the project to Heroku for live access. We’ve largely based this series on . Slack Starter Kit these video tutorials showcasing how to build Slack Apps , we got acquainted with the Slack Starter Kit and set up our development environment. , our Slack app issued a query to fetch data from a Salesforce org and then presented the result with UI components from Block Kit. Now, we’re going to extend that pattern to showcase how you can edit Salesforce data entirely within Slack. Let’s get started! In our first post In our second post Creating a shortcut Outside of the Block Kit UI, Slack has support for two other interactivity systems: and . Slash Commands are entered by a user in Slack’s text input (available in every channel), while shortcuts are graphical in nature. Since they’re easier to visualize, we’ll demonstrate shortcuts by creating a shortcut that will fetch our list of contacts and give us the ability to edit them. Slash Commands shortcuts Adding a shortcut (or a Slash Command, for that matter) first requires that you tell Slack the name of the command. Go to your app’s Overview page on Slack, and then click : Interactivity & Shortcuts Click , and select as your shortcut type, then click . On the next page, enter these values: Create New Shortcut Global Next : Edit contacts Name : Edits contacts on SFDC Short Description : Callback ID edit-contact-shortcut Click on , then click . Switch over to your Slack workspace, and click on the plus sign in the text area. You’ll be able to browse all of your workspace shortcuts. Here, you’ll also see the brand new shortcut you just created: Create Save Changes This shortcut doesn’t do anything yet, but this process is necessary for Slack to know about your shortcut. Next, let’s add the code to handle the event that fires whenever someone clicks on this shortcut. Wiring up the shortcut In your code editor, navigate to . This is the spot where we connect shortcut events to the code that’s executed. There’s already one shortcut given to us by the Starter Kit: . The given line suggests to us what we need to do: We call a function called and pass in a string and a function name. In this case, our string is the callback ID we previously defined, and our function name is the code we’ve yet to write. Change the file contents to look like this: apps/slack-salesforce-starter-app/listeners/shortcuts/index.js whoami shortcut const { whoamiCallback } = require('./whoami'); const { editContactCallback } = require('./edit-contact'); module.exports.register = (app) => { app.shortcut('who_am_i', whoamiCallback); app.shortcut('edit-contact-shortcut', editContactCallback); }; We’re laying the groundwork here by saying: “ run .” Slack App, if you get a shortcut with a callback ID of edit-contact-shortcut editContactCallback In this same folder, create a file called , and paste these lines into it: edit-contact.js 'use strict'; const { editContactResponse, authorize_sf_prompt } = require('../../user-interface/modals'); const editContactCallback = async ({ shortcut, ack, client, context }) => { try { await ack(); if (context.hasAuthorized) { const conn = context.sfconnection; await client.views.open({ trigger_id: shortcut.trigger_id, view: await editContactResponse(conn) }); } else { // Get BotInfo const botInfo = await client.bots.info({ bot: context.botId }); // Open a Modal with message to navigate to App Home for authorization await client.views.open({ trigger_id: shortcut.trigger_id, view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id) }); } } catch (error) { // eslint-disable-next-line no-console console.error(error); } }; module.exports = { editContactCallback }; Now, this might look intimidating, but most of it simply concerns the authentication boilerplate, ensuring that a user has an active SFDC connection. In the first logical path (if is ), we execute a function called , which accepts our open Salesforce ection. In the negative case, we ask the user to go to the Home tab to reauthenticate, just as we did in Part 1 of this tutorial. context.hasAuthorized true editContactResponse conn Navigate to the folder, and create a file called . Here, we’ll pop open a modal with contact information similar to the rows we saw in the Home tab in Part 2 of this tutorial: apps/slack-salesforce-starter-app/user-interface/modals edit-contact-response.js 'use strict'; const { Elements, Modal, Blocks } = require('slack-block-builder'); const editContactResponse = async (conn) => { const result = await conn.query( `Select Id, Name, Description FROM Contact` ); let records = result.records; let blockCollection = records.map((record) => { return Blocks.Section({ text: `*${record.Name}*\n${record.Description}` }).accessory( Elements.Button() .text(`Edit`) .actionId(`edit_contact`) .value(record.Id) ); }); return Modal({ title: 'Salesforce Slack App', close: 'Close' }) .blocks(blockCollection) .buildToJSON(); }; module.exports = { editContactResponse }; The main difference between the code in Part 2 and this block is that we’re using an array called , which lets us construct an array of blocks (in this case, Section blocks). knows how to take this array and transform it into a format that Slack understands, which makes it super simple to create data through a looped array, as we’ve done here. In Part 2 of our series, we constructed a giant string of data. By using , however, we can attach other Slack elements—such as buttons—which we’ve done here. blockCollection blocks BlockCollection Lastly, in , we’ll need to export this function, so that it can be imported by our function: apps/slack-salesforce-starter-app/user-interface/modals/index.js edit-contact.js 'use strict'; const { whoamiresponse } = require('./whoami-response'); const { editContactResponse } = require('./edit-contact-response'); const { authorize_sf_prompt } = require('./authorize-sf-prompt'); module.exports = { whoamiresponse, editContactResponse, authorize_sf_prompt }; After you’ve deployed this new code to Heroku via git push, switch to your Slack workspace and try executing the shortcut; you’ll be greeted with a dialog box similar to this one: Updating Salesforce data We’re able to fetch and display Salesforce data. Now, it’s time to connect the Edit button to change Salesforce data! Many of Slack’s interactive components have an , which, like the , serves to identify the element which a user acted upon. Just like everything else in the Starter Kit, there’s a special directory where you can define listeners for these action IDs: . In the file there, let’s add a new line that ties together the action ID with our yet-to-be-written functionality: action_id callback_id apps/slack-salesforce-starter-app/listeners/actions index.js 'use strict'; const { appHomeAuthorizeButtonCallback } = require('./app-home-authorize-btn'); const { editContactButtonCallback } = require('./edit-contact-btn'); module.exports.register = (app) => { app.action('authorize-with-salesforce', appHomeAuthorizeButtonCallback); app.action('edit_contact', editContactButtonCallback); }; In this same folder, create a new file called , and paste these lines into it: edit-contact-btn.js 'use strict'; const { editIndividualContact, authorize_sf_prompt } = require('../../user-interface/modals'); const editContactButtonCallback = async ({ body, ack, client, context }) => { const contactId = body.actions[0].value; try { await ack(); } catch (error) { // eslint-disable-next-line no-console console.error(error); } if (context.hasAuthorized) { const conn = context.sfconnection; const result = await conn.query( `SELECT Id, Name, Description FROM Contact WHERE Id='${contactId}'` ); let record = result.records[0]; await client.views.push({ trigger_id: body.trigger_id, view: editIndividualContact(record) }); } else { // Get BotInfo const botInfo = await client.bots.info({ bot: context.botId }); // Open a Modal with message to navigate to App Home for authorization await client.views.push({ trigger_id: body.trigger_id, view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id) }); } }; module.exports = { editContactButtonCallback }; The beginning and ending of this file should look familiar: We’re sending an response back to Slack to let it know that our app received the event payload (in this case, from clicking on the Edit button). We’re also checking whether or not we’re still authenticated. Here, we’re doing a single DB lookup using the ID of the contact, which we attached as a value to the Edit button when constructing our UI. ack This chunk of code creates another modal design which we need to define. Back in , create a file called and paste these lines into it: apps/slack-salesforce-starter-app/user-interface/modals edit-individual-contact.js 'use strict'; const { Elements, Modal, Blocks } = require('slack-block-builder'); const editIndividualContact = (record) => { return Modal({ title: 'Edit Contact', close: 'Close' }) .blocks( Blocks.Input({ blockId: 'description-block', label: record.Name }).element( Elements.TextInput({ placeholder: record.Description, actionId: record.Id }) ) ) .submit('Save') .callbackId('edit-individual') .buildToJSON(); }; module.exports = { editIndividualContact }; Here, we’ve created a modal with a single block: an input element. The element will be pre-populated with the contact’s description. We can edit this block and change the description to whatever we want. There are two important notes to point out in this code snippet: Notice that we’re attaching an to the input element. This is analogous to the ID we attached to the Edit button earlier, except this time, it’s dynamically generated based on the ID of the record we’re editing. actionId You’ll also notice that we have another ID, the , which is attached to the modal itself. Keep the existence of these IDs in the back of your mind: we’ll address both of these in a moment. For now, open up the file in this same directory, and this new modal-creating function: callbackID index.js require/export const { editIndividualContact } = require('./edit-individual-contact'); // ... module.exports = { whoamiresponse, editContactResponse, editIndividualContact, authorize_sf_prompt }; Now when you click the Edit button, you’ll be prompted to change the description: We now need to send this updated text to Salesforce. Click on the button and…nothing happens. Why? Well, Slack has a different set of events for interactions like these, called . The Starter Kit provides a good jumping off point when building an app, but it doesn’t handle every Slack use case, including this one. But that’s not a problem—we’ll add the functionality ourselves! Save view submissions Within the , create a new folder called . Just like before, our button here has an action ID to identify it: . We’ll head back into to configure this to a function: apps/slack-salesforce-starter-app/user-interface folder views Save edit-individual-contact apps/slack-salesforce-starter-app/listeners/actions/index.js const { editIndividualButtonCallback } = require('./edit-individual-btn); // ... app.action('edit-individual-contact', editIndividualButtonCallback); Create a new file called , and paste these lines into it: edit-individual-contact.js 'use strict'; const { submitEditCallback } = require('./submit-edit'); module.exports.register = (app) => { app.view('edit-individual', submitEditCallback); }; This format is identical to the other listeners provided by the Slack Starter Kit. The only difference is that we are calling the method. We also need to register this listener alongside the others. Open up , and the new view listener: view apps/slack-salesforce-starter-app/listeners/index.js require/register const viewListener = require('./views'); module.exports.registerListeners = (app) => { // ... viewListener.register(app); }; Next, in , create another file called and paste these lines into it: apps/slack-salesforce-starter-app/listeners/views submit-edit.js 'use strict'; const { editContactResponse, authorize_sf_prompt } = require('../../user-interface/modals'); const submitEditCallback = async ({ view, ack, client, context }) => { try { await ack(); } catch (error) { // eslint-disable-next-line no-console console.error(error); } if (context.hasAuthorized) { const contactId = view.blocks[0].element.action_id; const newDescription = view.state.values['description-block'][contactId].value; const conn = context.sfconnection; await conn.sobject('Contact').update({ Id: contactId, Description: newDescription }); await client.views.open({ trigger_id: view.trigger_id, view: await editContactResponse(conn) }); } else { // Get BotInfo const botInfo = await client.bots.info({ bot: context.botId }); // Open a Modal with message to navigate to App Home for authorization await client.views.push({ trigger_id: view.trigger_id, view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id) }); } }; module.exports = { submitEditCallback }; Let’s discuss those IDs that we set before. When Slack sends event payloads over to our app, it automatically generates an ID for every input element by default. That’s because Slack doesn’t know what the underlying data . It’s your responsibility to name the elements via action IDs. Slack uses these IDs as keys to populate the payload. When you receive Slack’s payload, you can use the keys you provided to parse the data the user entered. is Now, if you go through the flow to edit your contact’s description, you’ll notice that the modal will correctly save. To verify that the data on the Salesforce side was updated, run in your terminal and navigate to the Contacts tab. sfdx force:org:open Conclusion The Slack Starter Kit has made it an absolute breeze to build a Slack App that listens to user events. Beyond that, though, it also makes interacting with Salesforce and Heroku an absolute pleasure. We’ve covered just about everything that the Starter Kit can do. If you’d like to learn more about how Slack and Salesforce can work together, check out our blog post on . Also, the backend framework that interacts with Salesforce is the wonderful project. Be sure to check out both its documentation and to learn more about what you can build! building better together JSforce the Salesforce API Reference