Discover Lightning Actions
Learning Objectives
- Create an Aura component and action to populate fields based on user input and provide feedback messages to users.
- Create an Aura component and action that integrate with a third-party system.
- Start migrating your JavaScript custom button functionality to Lightning-friendly alternatives.
Lightning Actions: Smart, Fast, and Mobile

Note
As of the Spring ‘19 release (API version 45.0), you can build Lightning components using two programming models: the Lightning Web Components model and the original Aura Components model. Lightning web components and Aura components can coexist and interoperate on a page. This content covers Aura components.
Lightning actions are built on the existing Lightning Component framework. You can easily convert your existing Aura components to actions and use them in the Salesforce mobile app and Lightning Experience.
You make an Aura component invokable as an action by adding one of two interfaces to the component: force:lightningQuickAction or force:lightningQuickActionWithoutHeader. The first interface adds the standard header with Save and Cancel buttons to the Lightning action overlay, and the other doesn’t.
Another useful interface for a Lightning action is force:hasRecordId, which provides the record context to the component when it’s invoked from a record page. If you set up your Lightning action as a global quick action, you don’t need the record context. However, if you want to access a record’s data or metadata, you must implement force:hasRecordId.

Note
If you haven’t built Aura components before, visit the Lightning Development Center , do the Aura Components Quick Start project, and complete the Aura Components Basics module.
- Populating fields based on user input and providing feedback messages during data entry
- Integrating third-party APIs
Populate Fields Based on User Input and Provide Feedback Messages to Users
We’ve built a custom object in our sample org called Case Study, which we use to track different research projects. When we add new test users, we capture their names and email addresses. Since the email address is our primary contact method, we want to make sure it’s entered correctly. We also want to create a unique nickname for test users so that they can remain anonymous.
We’re going to create a Lightning action that displays the name and email address fields, validates the email address, and uses the first name and a random number to automatically create the nickname.
- CreateUser.cmp—The Lightning component that you see when you open the action. It contains the UI implementation, which includes the text fields, buttons, action title, and so on.
- CreateUserController.js—The controller that listens to the component and any UI events that take place, such as the initial load, text input, and button clicks. Its function is to delegate these events to the helper class, CreateUserHelper.js.
- CreateUserHelper.js—The code that deals with all the business logic, such as validating the password and email fields.
CreateUser.cmp
<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId"> <aura:attribute name="user" type="Test_User__c" default="{}"/> <aura:attribute name="hasErrors" type="Boolean" description="Indicate whether there were failures or not" /> <aura:attribute name="caseStudy" type="String" /> <aura:attribute name="recordId" type="String"/> <aura:attribute name="errorFromCreate" type="String"/> <!-- <aura:handler name="init" value="{!this}" action="{!c.init}" /> --> <force:recordData aura:id="frd" mode="EDIT" layoutType="FULL"/> <div class="slds-page-header" role="banner"> <p class="slds-text-heading--label">Case Study</p> <h1 class="slds-page-header__title slds-m-right--small slds-truncate slds-align-left" title="Case Study Title">{!v.caseStudy}</h1> </div> <br/> <aura:if isTrue="{!v.hasErrors}"> <div class="userCreateError"> <ui:message title="Error" severity="error" closable="true"> Please review the error messages. </ui:message> </div> </aura:if> <div class="slds-form--stacked"> <div class="slds-form-element"> <label class="slds-form-element__label" for="firstName">Enter first name: </label> <div class="slds-form-element__control"> <ui:inputText class="slds-input" aura:id="firstName" value="{!v.user.First}" required="true" keydown="{!c.updateNickname}" updateOn="keydown"/> </div> </div> <div class="slds-form-element"> <label class="slds-form-element__label" for="lastName">Enter last name: </label> <div class="slds-form-element__control"> <ui:inputText class="slds-input" aura:id="lastName" value="{!v.user.Last}" required="true" /> </div> </div> <div class="slds-form-element"> <label class="slds-form-element__label" for="nickname">Enter nickname: </label> <div class="slds-form-element__control"> <ui:inputText class="slds-input" aura:id="nickname" value="{!v.user.Nickname}" required="false"/> </div> </div> <div class="slds-form-element"> <label class="slds-form-element__label" for="userEmail">Enter user's email:</label> <div class="slds-form-element__control"> <ui:inputEmail class="slds-input" aura:id="userEmail" value="{!v.user.Email__c}" required="true"/> </div> </div> <div class="slds-form-element"> <label class="slds-form-element__label" for="userPassword">Enter user's password:</label> <div class="slds-form-element__control"> <ui:inputSecret class="slds-input" aura:id="userPassword" value="{!v.user.Password__c}" required="true"/> </div> </div> <div class="slds-form-element"> <ui:button class="slds-button slds-button--neutral" press="{!c.cancel}" label="Cancel" /> <ui:button class="slds-button slds-button--brand" press="{!c.saveUserForm}" label="Save User" /> </div> </div> </aura:component>
CreateUserController.js
({ /** * Auto generate the username from firstName on tab */ updateNickname: function(component, event, helper) { // Update the nickname field when 'tab' is pressed if (event.getParams().keyCode == 9) { var nameInput = component.find("firstName"); var nameValue = nameInput.get("v.value"); var nickName = component.find("nickname"); var today = new Date(); nickName.set("v.value", nameValue + today.valueOf(today)); } }, /** * Capture the Inputs and invoke the helper.save with the input params */ saveUserForm : function(component, event, helper) { var name = component.get("v.user.First"); var last = component.get("v.user.Last"); var password = component.get("v.user.Password__c"); var email = component.get("v.user.Email__c"); var nickname = component.get("v.user.Nickname"); var passwordCmp = component.find("userPassword"); var emailCmp = component.find("userEmail"); helper.validatePassword(component, event, helper); helper.validateEmail(component, event, helper); if (passwordCmp.get("v.errors") == null && emailCmp.get("v.errors") == null) { component.set("v.hasErrors", false); helper.save(component, name + " " + last, password, email, nickname); } else { component.set("v.hasErrors", true); } }, cancel : function(component, event, helper) { $A.get("e.force:closeQuickAction").fire(); } })
CreateUserHelper.js
({ save: function(component, name, password, email, nickname) { // Create a user record, save it, and close the panel var userRecord = {apiName: 'Test_User__c', fields: {}}; userRecord.fields.Name = {value: name}; userRecord.fields.Password__c = {value: password}; userRecord.fields.Email__c = {value: email}; userRecord.fields.Nickname__c = {value: nickname}; userRecord.fields.Case_Study__c = {value: component.get("v.recordId")}; // get the force:recordData and set the targetRecord component.find("frd").set('v.targetRecord', userRecord); // invoke saveRecord of force:recordData component.find("frd").saveRecord($A.getCallback(function(response) { if (component.isValid() && response.state == "SUCCESS") { $A.get("e.force:closeQuickAction").fire(); var toastEvent = $A.get("e.force:showToast"); toastEvent.setParams({ "title": "Success!", "message": "The test user has been created." }); toastEvent.fire(); $A.get('e.force:refreshView').fire(); } else if (response.state == "ERROR") { console.log('There was a problem and the state is: '+ response.state); } })); }, validatePassword : function(component, event, helper) { var inputCmp = component.find("userPassword"); var value = inputCmp.get("v.value"); if (value == undefined) { inputCmp.set("v.errors", [{message: "You must enter a password."}]); } else if (value.length < 7 || value.length > 15) { inputCmp.set("v.errors", [{message: "The password is the wrong length (must be <= 15): " + value.length}]); } else if (value.search(/[0-9]+/) == -1) { inputCmp.set("v.errors", [{message: "The password must contain at least one number."}]); } else if (value.search(/[a-zA-Z]+/) == -1) { inputCmp.set("v.errors", [{message: "The password must contain at least one letter."}]); } else { inputCmp.set("v.errors", null); } }, validateEmail : function(component, event, helper) { var inputCmp = component.find("userEmail"); var value = inputCmp.get("v.value"); if (value == undefined) { inputCmp.set("v.errors", [{message: "You must enter an email."}]); return; } var apos = value.indexOf("@"); var dotpos = value.lastIndexOf("."); if (apos<1||dotpos-apos<2){ inputCmp.set("v.errors", [{message: "Email is not in the correct format: " + value}]); } else if (value.substring(apos+1, dotpos) != "gmail") { inputCmp.set("v.errors", [{message: "Email must be a gmail account: " + value.substring(apos+1, dotpos)}]); } else { inputCmp.set("v.errors", null); } } })
After creating the Lightning component, we assign it to an action. In the object management settings for Case Study, we go to Buttons, Links, and Actions, click New Action, then configure the action with these parameters.
Field | Value |
---|---|
Object Name | Case Study |
Action Type | Lightning Component |
Lightning Component | c:CreateUser |
Height | 500px |
Label | Create Test User |
Name | CreateUser |
Then we add our new Lightning action to the Case Study page layout. When users invoke it from a case study record page, they see the Lightning action that we created.
The great thing about this Lightning action is that it also works in the Salesforce mobile app.
Integrate Third-Party APIs
Let’s look at how to integrate with Twilio for sending SMS messages. In this example, we’re in the luxury travel business, and we handle celebrities and VIPs. We want our customer service agents to be able to communicate with their clients, but we don’t want to expose the clients’ personal contact information to anyone. We create a Lightning action on the Contact object so that agents can send messages without seeing the contact’s phone number.
As in our case study example, we’ll create the Aura component and helper classes, and then create a quick action to invoke the component.
This Lightning action is composed of the Aura component, a JavaScript controller, and an Apex controller, which references library classes that handle the Twilio integration.
SendTwilioSMS.cmp
<aura:component controller="TwilioSendSMSController" implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:lightningQuickAction" > <aura:attribute name="textMessage" type="String" /> <aura:attribute name="destinationNumber" type="String" /> <aura:attribute name="messageError" type="Boolean" /> <aura:attribute name="recordId" type="String" /> <aura:handler name="init" value="{!this}" action="{!c.init}" /> <aura:if isTrue="{!v.messageError}"> <!-- Load error --> <div class="userCreateError"> <ui:message title="Error" severity="error" closable="true"> Unable to send message. Please review your data and try again. </ui:message> </div> </aura:if> <div class="slds-form--stacked"> <label class="slds-form-element__label" for="instructMsg">Please enter the message (max 160 char) below: </label> <br/> <div class="slds-form-element__control"> <ui:inputText class="slds-input" aura:id="message" label="Text Message" value="{!v.textMessage}" required="true" maxlength="160" size="165" /> </div> <div class="centered"> <ui:button class="slds-button slds-button--brand" press="{!c.sendMessage}" label="Send Message"/> </div> </div> </aura:component>
SendTwilioSmsController.js
({ init : function(component, event, helper) { var action = component.get("c.getPhoneNumber"); action.setParams({"contactId": component.get("v.recordId")}); action.setCallback(this, function(response) { var state = response.getState(); if(component.isValid() && state == "SUCCESS"){ component.set("v.destinationNumber", response.getReturnValue()); } else { component.set("v.messageError", true); } }); $A.enqueueAction(action); }, sendMessage : function(component, event, helper) { var smsMessage = component.get("v.textMessage"); var number = component.get("v.destinationNumber"); var recordId = component.get("v.recordId") var action = component.get("c.sendMessages"); action.setParams({"mobNumber": number, "message": smsMessage, "contactId": component.get("v.recordId")}); action.setCallback(this, function(response) { var state = response.getState(); if(component.isValid() && state == "SUCCESS"){ $A.get("e.force:closeQuickAction").fire(); var toastEvent = $A.get("e.force:showToast"); toastEvent.setParams({ "title": "Success!", "message": "SMS has been sent woo hoo!" }); toastEvent.fire(); } else { component.set("v.messageError", true); } }); $A.enqueueAction(action); } })
SendTwilioSmsController.apxc
/* * Apex controller that currently contains only one method to send sms message */ global class TwilioSendSMSController { /* * This method uses the Twilio for Salesforce library class and method to * send the message using the Twilio api. */ @AuraEnabled webService static String sendMessages(String mobNumber, String message, Id contactId) { System.debug('the mobNumber is: '+ mobNumber + ' and the message is: '+ message + ' and contactId is: ' + contactId); if (mobNumber == null) { mobNumber = getPhoneNumber(contactId); } try { TwilioRestClient client = TwilioAPI.getDefaultClient(); Map<String,String> params = new Map<String,String> { 'To' => mobNumber, 'From' => '15555551234', 'Body' => message }; TwilioSMS sms = client.getAccount().getSMSMessages().create(params); return sms.getStatus(); } catch(exception ex) { System.debug('oh no, it failed: '+ex); return 'failed'; } } @AuraEnabled public static String getPhoneNumber(Id contactId) { Contact currentRecord = [SELECT Phone FROM Contact WHERE Id = :contactId]; return currentRecord.Phone.replace(' ', '').replace('-', '').replace(')', '').replace('(', ''); } }
After creating the component, we create a Lightning action. This time we add it to the Contact page layout so agents can access it.
Again, the cool thing is that the action is also available in the Salesforce mobile app. If a car service provider is trying to contact the customer at the airport, the driver can use a mobile device to reach the customer easily.
Lightning actions are the future of programmatic actions in Lightning Experience. We hope that you start looking at this set of solutions as a better alternative to JavaScript buttons.
And if you depend on partner apps that use JavaScript buttons, you’ll be glad to know that many of our partners have already started migrating and upgrading their apps to Lightning. You can find more apps on the AppExchange that are updated for Lightning Experience.
Beyond the Basics
If you’d like to try Twilio for yourself, you can find an unmanaged package of the Twilio for Salesforce library class used in the third-party integration example in the Salesforce Github library: https://github.com/twilio/twilio-salesforce
Before using Twilio for Salesforce and the Twilio API, create a Twilio account, set up a phone number, and connect your account to Salesforce. See the TwilioApi.cls in the Github library for more detailed instructions.