Create Salesforce Validation Rules using natural language prompts - powered by AgentForce + Apex SOAP API
This implementation creates a dynamic validation rule generator that:
The main Apex class that handles validation rule creation via SOAP API:
/**
* Apex class to create Validation Rules using a direct SOAP API callout.
* This class is designed to be called from AgentForce with @InvocableMethod.
*/
public class ValidationRuleSOAPCreator {
public class ValidationRuleRequest {
@InvocableVariable(label='Validation Rule Name'
description='API name for the validation rule (PascalCase, no spaces). Example: HoursWorked_NegativeCheck'
required=true)
public String ValidationRuleName;
@InvocableVariable(label='Validation Rule Formula'
description='Salesforce formula that returns TRUE when validation fails. Example: Hours__c < 0'
required=true)
public String ValidationRuleFormula;
@InvocableVariable(label='Error Message'
description='User-friendly error message displayed when validation fails. Example: Hours Worked cannot be a negative number.'
required=true)
public String ErrorMessage;
@InvocableVariable(label='Rule Description'
description='Business description explaining what the rule enforces. Example: Validates that the Hours Worked field does not contain negative values.'
required=true)
public String RuleDescription;
@InvocableVariable(label='Error Display Field'
description='API name of the field where error message should be displayed. Example: Hours__c'
required=true)
public String errorDisplayField;
@InvocableVariable(label='Object Name'
description='API name of the Salesforce object for the validation rule. Example: Account, Contact, Opportunity'
required=true)
public String objectName;
}
public class ValidationRuleResponse {
@InvocableVariable(label='Success Status'
description='Boolean indicating whether the validation rule was created successfully')
public Boolean isSuccess;
@InvocableVariable(label='Validation Rule ID'
description='Full identifier of the created validation rule (ObjectName.RuleName)')
public String validationRuleId;
@InvocableVariable(label='Response Message'
description='Success or failure message from the validation rule creation operation')
public String message;
@InvocableVariable(label='Error Details'
description='List of detailed error messages if the operation failed')
public List<String> errors;
public ValidationRuleResponse() {
this.errors = new List<String>();
}
}
// Static endpoint for SOAP API metadata service
private static final String SOAP_ENDPOINT = URL.getOrgDomainUrl().toExternalForm() + '/services/Soap/m/59.0';
/**
* Invocable method that AgentForce will call
* This accepts a list of requests and returns a list of responses
*
* @param requests List of ValidationRuleRequest objects containing rule details
* @return List of ValidationRuleResponse objects with creation results
*/
@InvocableMethod(label='Create Validation Rule via SOAP'
description='Creates a validation rule using SOAP API . Accepts natural language requirements and generates technical validation rules.')
public static List<ValidationRuleResponse> createValidationRule(List<ValidationRuleRequest> requests) {
List<ValidationRuleResponse> responses = new List<ValidationRuleResponse>();
for (ValidationRuleRequest request : requests) {
responses.add(createValidationRuleViaSOAP(request));
}
return responses;
}
/**
* Main method to create validation rule via SOAP API
*/
public static ValidationRuleResponse createValidationRuleViaSOAP(ValidationRuleRequest request) {
ValidationRuleResponse response = new ValidationRuleResponse();
try {
// 1. Validate the request
List<String> validationErrors = validateRequest(request);
if (!validationErrors.isEmpty()) {
response.isSuccess = false;
response.errors = validationErrors;
response.message = 'Request validation failed';
return response;
}
// 2. Construct the SOAP XML Request Body
String soapRequestBody = buildSOAPRequest(request);
System.debug('soapRequestBody-------------'+soapRequestBody);
debugSOAPRequest(soapRequestBody);
// 3. Create and execute the HTTP Request
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(SOAP_ENDPOINT);
httpReq.setMethod('POST');
httpReq.setHeader('Content-Type', 'text/xml; charset=UTF-8');
httpReq.setHeader('SOAPAction', 'create');
httpReq.setBody(soapRequestBody);
// 4. Get the current session ID for authentication
String sessionId = loginToSalesforce();
if (String.isBlank(sessionId)) {
response.isSuccess = false;
response.message = 'Could not obtain a valid session ID for API authentication.';
return response;
}
// 5. Execute the callout
Http http = new Http();
HttpResponse httpRes = http.send(httpReq);
// 6. Parse the SOAP Response
if (httpRes.getStatusCode() == 200) {
Dom.Document responseDoc = httpRes.getBodyDocument();
Dom.XmlNode rootNode = responseDoc.getRootElement();
// Check for SOAP Faults
Boolean hasFault = false;
for (Dom.XmlNode node : rootNode.getChildElements()) {
if (node.getName() == 'Body') {
for (Dom.XmlNode bodyNode : node.getChildElements()) {
if (bodyNode.getName() == 'Fault') {
hasFault = true;
for (Dom.XmlNode faultNode : bodyNode.getChildElements()) {
if (faultNode.getName() == 'faultstring') {
response.errors.add('SOAP Fault: ' + faultNode.getText());
}
}
}
}
}
}
if (hasFault) {
response.isSuccess = false;
response.message = 'SOAP API call returned a fault.';
} else {
response.isSuccess = true;
response.validationRuleId = request.objectName + '.' + request.ValidationRuleName;
response.message = 'Validation Rule submitted successfully via SOAP API.';
}
} else {
response.isSuccess = false;
response.message = 'HTTP Error: ' + httpRes.getStatusCode() + ' ' + httpRes.getStatus();
response.errors.add('Response Body: ' + httpRes.getBody());
}
} catch (Exception e) {
response.isSuccess = false;
response.message = 'Exception occurred: ' + e.getMessage();
response.errors.add(e.getStackTraceString());
}
return response;
}
/**
* Builds the complete SOAP XML request envelope for the create() call.
*/
private static String buildSOAPRequest(ValidationRuleRequest request) {
String sessionId = loginToSalesforce();
String soapRequest =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" ' +
'xmlns:met="http://soap.sforce.com/2006/04/metadata" ' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
'<soapenv:Header>' +
'<met:SessionHeader>' +
'<met:sessionId>' + sessionId + '</met:sessionId>' +
'</met:SessionHeader>' +
'</soapenv:Header>' +
'<soapenv:Body>' +
'<met:create>' +
'<met:metadata xsi:type="met:ValidationRule">' +
'<fullName>' + escapeXml(request.objectName + '.' + request.ValidationRuleName) + '</fullName>' +
'<active>true</active>' +
'<description>' + escapeXml(request.RuleDescription) + '</description>' +
'<errorMessage>' + escapeXml(request.ErrorMessage) + '</errorMessage>' +
'<errorConditionFormula>' + escapeXml(request.ValidationRuleFormula) + '</errorConditionFormula>' +
'</met:metadata>' +
'</met:create>' +
'</soapenv:Body>' +
'</soapenv:Envelope>';
return soapRequest;
}
/**
* Debug method to log the SOAP request
*/
private static void debugSOAPRequest(String soapRequest) {
String debugRequest = soapRequest.length() > 1000 ? soapRequest.substring(0, 1000) + '...' : soapRequest;
System.debug('SOAP Request: ' + debugRequest);
if (soapRequest.contains('&') && !soapRequest.contains('&')) {
System.debug('WARNING: Unescaped ampersand found in request');
}
}
/**
* Gets session ID for authentication
*/
private static String getSessionId() {
return loginToSalesforce();
}
/**
* Helper function to escape XML special characters
*/
private static String escapeXml(String input) {
if (input == null) return '';
return input
.replace('&', '&')
.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace('\'', ''');
}
/**
* Validate the incoming request
*/
private static List<String> validateRequest(ValidationRuleRequest request) {
List<String> errors = new List<String>();
if (request == null) {
errors.add('Request cannot be null');
return errors;
}
if (String.isBlank(request.ValidationRuleName)) errors.add('ValidationRuleName is required');
if (String.isBlank(request.ValidationRuleFormula)) errors.add('ValidationRuleFormula is required');
if (String.isBlank(request.ErrorMessage)) errors.add('ErrorMessage is required');
if (String.isBlank(request.objectName)) errors.add('objectName is required');
if (String.isBlank(request.errorDisplayField)) errors.add('errorDisplayField is required');
// Validate object exists
if (String.isNotBlank(request.objectName)) {
Map<String, Schema.SObjectType> globalDescribe = Schema.getGlobalDescribe();
if (!globalDescribe.containsKey(request.objectName)) {
errors.add('Object ' + request.objectName + ' does not exist');
}
}
return errors;
}
/**
* Check the status of an async metadata deployment
* This can also be made invocable if needed
*/
public static void checkDeploymentStatus(String asyncResultId) {
String sessionId = loginToSalesforce();
String soapRequest =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" ' +
'xmlns:met="http://soap.sforce.com/2006/04/metadata">' +
'<soapenv:Header>' +
'<met:SessionHeader>' +
'<met:sessionId>' + sessionId + '</met:sessionId>' +
'</met:SessionHeader>' +
'</soapenv:Header>' +
'<soapenv:Body>' +
'<met:checkStatus>' +
'<met:asyncProcessId>' + asyncResultId + '</met:asyncProcessId>' +
'</met:checkStatus>' +
'</soapenv:Body>' +
'</soapenv:Envelope>';
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(SOAP_ENDPOINT);
httpReq.setMethod('POST');
httpReq.setHeader('Content-Type', 'text/xml; charset=UTF-8');
httpReq.setHeader('SOAPAction', 'checkStatus');
httpReq.setBody(soapRequest);
Http http = new Http();
HttpResponse httpRes = http.send(httpReq);
System.debug('Deployment Status Response: ' + httpRes.getBody());
}
public static String loginToSalesforce() {
//You can follow other authentication mechanism like Oauth 2.0, here for example purpose I'm obtaining the session id using username and password with security token
String username = 'Your_Org_Admin_User_Name';
String passwordWithToken = 'Password+SecurityToken';//Example here Test@123 is password and Sx1TfjcEgoCxv5Ax8VLI1xsfd is security token e.g: 'Test@123Sx1TfjcEgoCxv5Ax8VLI1xsfd';
String soapEndpoint = 'https://login.salesforce.com/services/Soap/u/59.0';
HttpRequest req = new HttpRequest();
req.setEndpoint(soapEndpoint);
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
req.setHeader('SOAPAction', 'login');
// Construct the SOAP request body
String soapBody = '<?xml version="1.0" encoding="UTF-8"?>' +
'<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">' +
' <env:Body>' +
' <n1:login xmlns:n1="urn:partner.soap.sforce.com">' +
' <n1:username>' + username + '</n1:username>' +
' <n1:password>' + passwordWithToken + '</n1:password>' +
' </n1:login>' +
' </env:Body>' +
'</env:Envelope>';
req.setBody(soapBody);
Http http = new Http();
HttpResponse res = new HttpResponse();
String sessionId = '';
try {
res = http.send(req);
String responseBody = res.getBody();
System.debug('Login Response Body: ' + responseBody);
if (res.getStatusCode() == 200) {
// Extract sessionId from the XML response
sessionId = extractSessionId(responseBody);
System.debug('Session ID: ' + sessionId);
} else {
System.debug('Login Failed: ' + res.getStatusCode() + ' - ' + responseBody);
}
} catch (Exception e) {
System.debug('Error during login: ' + e.getMessage());
}
return sessionId;
}
public static String extractSessionId(String responseXml) {
Pattern sessionIdPattern = Pattern.compile('<sessionId>(.*?)</sessionId>');
Matcher matcher = sessionIdPattern.matcher(responseXml);
if (matcher.find()) {
return matcher.group(1);
}
return '';
}
}
The template that guides the AI in generating validation formulas:
You are a Salesforce formula expert specializing in complex validation rules. Generate a validation formula for the following requirement.
Context:
- Object: {!$Input:ObjectName}
- Primary Field: {!$Input:FieldName}
- Related Fields: {!$Input:RelatedFields}
- Validation Requirement: {!$Input:UserDescription}
- Business Context: {!$Input:BusinessContext}
## Guidelines for Formula Generation:
1. The formula must return TRUE when the validation FAILS.
2. Handle null values safely using ISBLANK() or ISNULL().
3. Use correct data type functions (e.g., VALUE(), DATEVALUE(), TEXT()).
4. Support multi-field dependencies with AND(), OR(), NOT().
5. Implement regex checks when formatting rules are required.
6. Ensure formula is optimized and free from syntax errors.
7. Always consider edge cases (e.g., blank fields, invalid ranges, wrong formats).
8. Provide a clear and professional error message.
9. Provide a short business-friendly description for the rule.
10. Suggest a **Validation Rule Name** following Salesforce naming best practices:
- PascalCase (e.g., `AccountNameLengthCheck`)
- Avoid spaces or special characters
- Short but descriptive
11. Always return two additional fields:
- **errorDisplayField**: The API name of the field where the error will be displayed (default to Primary Field if not specified).
- **objectName**: The Salesforce object API name where the rule will be created.
## Expected Output Mapping (JSON-like format):
{
"ValidationRuleName": "<Suggested Validation Rule API name>",
"ValidationRuleFormula": "<Generated Salesforce formula here>",
"ErrorMessage": "<Human-readable error message to show end users>",
"RuleDescription": "<Short description of what the rule enforces>",
"errorDisplayField": "<API name of field where error is displayed>",
"objectName": "<Object API name where rule is created>"
}
## Task:
Generate the Validation Rule Name, Formula, Error Message, Description, errorDisplayField, and objectName for: {!$Input:UserDescription}
Topic Name: Dynamic Validation Rule Creator
Description: Automatically generates and deploys Salesforce validation rules from natural language requirements using AI-powered formula generation and SOAP API deployment.
Scope: This topic handles the complete lifecycle of validation rule creation - from interpreting business requirements to deploying working validation rules in Salesforce.
You are a Salesforce validation rule expert that helps users create and deploy validation rules automatically.
Your role:
1. Accept natural language descriptions of validation requirements
2. Generate proper Salesforce validation formulas
3. Deploy the validation rules directly to Salesforce via SOAP API
4. Provide clear feedback on success or failure
Always maintain data accuracy and follow Salesforce best practices for validation rule creation.
When users provide validation requirements, first generate the technical specifications, then deploy the rule automatically.
Action Name: Generate Validation Rule Specifications
Action Type: Flex Prompt
Description: Converts natural language validation requirements into technical Salesforce validation rule specifications
Action Name: Deploy Validation Rule to Salesforce
Action Type: Apex (Invocable Method)
Description: Takes the generated validation rule specifications and deploys them to Salesforce using SOAP API
Class: ValidationRuleSOAPCreator
Method: createValidationRule
Maps the output from Action 1 directly to the Apex method input parameters
{
"ValidationRuleName": "ClosedWon_AmountDateCheck",
"ValidationRuleFormula": "AND(TEXT(StageName) = \"Closed Won\", OR(ISBLANK(CloseDate), Amount <= 1000))",
"ErrorMessage": "When Stage is Closed Won, Close Date is required and Amount must be greater than 1000.",
"RuleDescription": "Ensures that Closed Won Opportunities have a Close Date and Amount > 1000.",
"errorDisplayField": "CloseDate",
"objectName": "Opportunity"
}