Hello friends, today we will discuss Invoke Tooling API From LWC Salesforce. While Apex provides a robust set of features, there are scenarios where advanced development tasks demand more flexibility and control. This is where the Tooling API steps in as a valuable resource.
The Tooling API is a RESTful API offered by Salesforce, specifically designed to interact with the metadata and tooling aspects of the platform programmatically.
Also, check this: Run Flow Asynchronously In Salesforce
Key Highlights :
- Perform a wide range of tasks, including querying metadata, deploying changes, running tests, and more.
- We can create execute code window and run the code.
- Tooling API allows developers to dynamically query metadata information using SOQL.
- Can query List View and fetch query.
- The Tooling API simplifies this process by providing REST endpoints to deploy metadata changes programmatically.
- The Tooling API enables developers to run tests programmatically, retrieve test results, and perform test-related operations.
Code :
Here we will create a Execute anonymous code window that can run any code like the developer console in Salesforce. We will create an LWC component for the front-end window and in the backend, we call tooling API in apex to perform code.
First of all, create a VF page to get the Session-Id of the current Logged In User, We can also authorize the tooling API by connected App and name credential
GetSessionId.vfp :
<apex:page > Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id </apex:page>
UtilsGetSessionId.cls: here we get the session Id from the VF page.
/** * @description : This class is used to get Session Id of logged In User. * @coverage : ExecuteCodeCtrlTest | @CC100% * @author : Techdicer * @group : Techdicer * @last modified on : 06-19-2023 * @last modified by : Techdicer * Modifications Log * Ver Date Author Modification * 1.0 06-16-2023 Techdicer Initial Version **/ global class UtilsGetSessionId{ /** * @description get Session Id of logged In User. * @author Techdicer | 06-16-2023 * @param PageReference visualforcePage name * @return String **/ global static String getSessionIdFromVFPage(PageReference visualforcePage){ String content = Test.isRunningTest() ? 'Start_Of_Session_Idtest1234567889999999999999End_Of_Session_Id' : visualforcePage.getContent().toString(); Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(), e = content.indexOf('End_Of_Session_Id'); return content.substring(s, e); } }
ExecuteCodeCtrl.cls :
/** * @description : This class is used to fetch metadata and execute apex code. * @coverage : ExecuteCodeCtrlTest | @CC90% * @author : Techdicer * @group : Techdicer * @last modified on : 06-21-2023 * @last modified by : Techdicer * Modifications Log * Ver Date Author Modification * 1.0 06-21-2023 Techdicer Initial Version **/ public Without Sharing class ExecuteCodeCtrl { /** * @description execute the apex code by tooling API * @author Techdicer | 06-21-2023 * @param String script * @return String success/error info **/ @AuraEnabled public static String executeCode(String script) { //String baseURL='callout:Execute_Code/?'; //is use when you create connected salesforce app and name creds String baseURL = URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v57.0/tooling/executeAnonymous/?'; Http h = new Http(); HTTPRequest req = new HTTPRequest(); //set endpoint with script code req.setEndpoint(baseUrl + 'anonymousBody=' + EncodingUtil.urlEncode(script, 'UTF-8')); req.setMethod('GET'); req.setTimeout(120000); // OAuth header req.setHeader('Authorization','Bearer ' + UtilsGetSessionId.getSessionIdFromVFPage(Page.GetSessionId)); HttpResponse res = h.send(req); system.debug('Session Id - ' + res.getBody()); System.debug(res.getBody()); // Response to a execute successful should be 201 if (res.getStatusCode() != 200 && res.getStatusCode() != 201) { System.debug(res.getBody()); throw newMessageException('Error: Code did not execute. Please check your code | Status code : ' + res.getStatus()); } else { //parse the json response body WrapperClass wrap = parse(res.getBody()); if(wrap.success){ return 'Successfully executed the Apex script code.'; } else{ String errorMessage = wrap.compileProblem != null ? wrap.compileProblem : (wrap.exceptionMessage != null ? wrap.exceptionMessage : ''); throw newMessageException('Error: Code did not execute. Please check your code | Issue : ' + errorMessage); } } } /** * @description used to manage error message * @author Techdicer | 06-21-2023 * @param String message * @return AuraHandledException **/ private static AuraHandledException newMessageException(String message) { AuraHandledException e = new AuraHandledException(message); e.setMessage(message); return e; } /** * @description wrapper class for parse the JSON resonse * @author Techdicer | 06-21-2023 **/ public class WrapperClass { public Integer line; public Integer column; public Boolean compiled; public Boolean success; public String compileProblem; public String exceptionStackTrace; public String exceptionMessage; } /** * @description used to parse the JSON resonse * @author Techdicer | 06-21-2023 * @param String json * @return WrapperClass **/ public static WrapperClass parse(String json) { return (WrapperClass) System.JSON.deserialize(json, WrapperClass.class); } }
executeCodeLWC.HTML :
<!-- @description : This html template is used to show Execute code screen with button. @author : Rijwan @group : Techdicer @last modified on : 06-21-2023 @last modified by : Rijwan Modifications Log Ver Date Author Modification 1.0 06-21-2023 Rijwan Initial Version | Story Number --> <template> <lightning-card variant="Narrow" title="Execute Code" icon-name="standard:apex"> <!-- loader --> <div if:true={showSpinner}> <lightning-spinner alternative-text="Loading..." variant="brand" class="slds-is-fixed"> </lightning-spinner> </div> <!-----/loader--------> <div class="slds-p-horizontal_medium"> <lightning-textarea name="Code" label="Code" data-id="code" value={scriptCode} onchange={handleFieldChange} class="scriptCode" required> </lightning-textarea> <div class="slds-text-align_center slds-m-top_small"> <lightning-button variant="brand" label="Execute Code" title="Execute Code" onclick={handleExecute} disabled={isDisableExecuteButton}> </lightning-button> </div> </div> </lightning-card> </template>
executeCodeLWC.JS:
/** * @description : This JS file is used to fetch the metadata, call execute method in apex class. * @author : Techdicer * @group : Techdicer * @last modified on : 06-21-2023 * @last modified by : Techdicer * Modifications Log * Ver Date Author Modification * 1.0 06-21-2023 Techdicer Initial Version **/ import { LightningElement, track, wire, api } from 'lwc'; import executeCode from "@salesforce/apex/ExecuteCodeCtrl.executeCode"; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; export default class ExecuteCodeLWC extends LightningElement { @track scriptCode; @track data; output; showSpinner = false; /** * @description handleFieldChange for detected field change of text area * @author Rijwan | 06-21-2023 * @summary in this method assign the value in scriptCode variable */ handleFieldChange(event) { this.scriptCode = event.detail.value; } /** * @description handleValidation for put validation on text area field used to write script code * @author Rijwan | 06-21-2023 * @param message error message * @param isError show error or not * @summary here we pass params so according these we custom validate the script code text area field */ handleValidation(message, isError) { let scriptCode = this.template.querySelector(".scriptCode"); if (isError) { scriptCode.setCustomValidity(message); } else { scriptCode.setCustomValidity(""); // clear previous value } scriptCode.reportValidity(); } /** * @description handleExecute called when we click execute button * @author Rijwan | 06-21-2023 * @summary in this method we call apex method executeCode and pass paramters which is scriptCode to run the script. */ handleExecute(event) { this.showSpinner = true; executeCode({ script: this.scriptCode }) .then(res => { this.showToast('Success', res, 'success', 'dismissable'); this.handleValidation('', false); }).catch(err => { this.showToast('Error', err.body.message, 'error', 'dismissable'); this.handleValidation(err.body.message, true); }).finally(() => { this.showSpinner = false; }) } /** * @description isDisableExecuteButton * @author Rijwan | 06-21-2023 * @summary this is used for enable/disable Execute Code button in HTML template according text area scriptCode value */ get isDisableExecuteButton() { return !this.scriptCode || this.scriptCode == undefined; } /** * @description showToast is used show toast message * @author Rijwan | 06-16-2023 * @param title toast title * @param message message to show * @param variant type of toast * @param mode dismissable/sticky * @summary here we pass params so according these we show the toast message. */ showToast(title, message, variant, mode) { const evt = new ShowToastEvent({ title: title, message: message, variant: variant, mode: mode }); this.dispatchEvent(evt); } }
executeCodeLWC.CSS:
:host { --slds-c-textarea-sizing-min-height: 500px; }
executeCodeLWC.JS-meta.xml:
<?xml version="1.0"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>57.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__HomePage</target> </targets> </LightningComponentBundle>
2 comments
That was a good article but it doesn’t seem to work anymore. Salesforce has removed access to the Session Id through Apex or the VF page for a lwc. You will have to use Named Credentials to access the Tooling API through a lwc now.
In that case you have to create connected app for session Id