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