.cursor/skills/sf-approval-processes/SKILL.md
Use when designing Salesforce Apex approval processes, multi-step approvals, or Flow-integrated submissions. Do NOT use for general Apex or Flow-only work.
npx skillsauth add jiten-singh-shahi/salesforce-claude-code sf-approval-processesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@../_reference/APPROVAL_PROCESSES.md
┌─────────────────────────────────────────────────────┐
│ Approval Process │
├─────────────────────────────────────────────────────┤
│ Entry Criteria │ WHO can submit? WHEN? │
│ Initial Submitter │ Record owner, specific users │
├─────────────────────────────────────────────────────┤
│ Step 1: Manager │ Approver: Manager field │
│ Step 2: VP │ Approver: Related user field │
│ Step 3: Finance │ Approver: Queue │
├─────────────────────────────────────────────────────┤
│ Initial Actions │ Lock record, set Status │
│ Approval Actions │ Unlock, update field, email │
│ Rejection Actions │ Unlock, set Status to Rejected │
│ Recall Actions │ Unlock, clear approval fields │
└─────────────────────────────────────────────────────┘
Amount__c > 10000 AND Status__c = 'Draft'
RecordType.Name = 'Enterprise' AND Discount__c > 20
Formula:
AND(Amount__c > 10000, ISPICKVAL(Status__c, 'Draft'), NOT(ISBLANK(OwnerId)))
| Property | Options | |---|---| | Approver | User field, Manager field, Queue, Related user | | Criteria | All records OR filter criteria (step-specific) | | Reject behavior | Final rejection OR go to previous step | | Unanimity | All must approve OR first response |
Status__c = "Pending Approval"Status__c = "Approved"Status__c = "Rejected"public class ApprovalService {
public static void submitForApproval(Id recordId, String comments) {
Approval.ProcessSubmitRequest request = new Approval.ProcessSubmitRequest();
request.setObjectId(recordId);
request.setSubmitterId(UserInfo.getUserId());
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (result.isSuccess()) {
System.debug('Submitted. Instance ID: ' + result.getInstanceId());
} else {
for (Database.Error err : result.getErrors()) {
System.debug(LoggingLevel.ERROR, 'Failed: ' + err.getMessage());
}
}
}
}
public static void approveRecord(Id workItemId, String comments) {
Approval.ProcessWorkitemRequest request = new Approval.ProcessWorkitemRequest();
request.setWorkitemId(workItemId);
request.setAction('Approve');
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (!result.isSuccess()) {
throw new ApprovalException('Approval failed: ' + result.getErrors());
}
}
public static void rejectRecord(Id workItemId, String comments) {
Approval.ProcessWorkitemRequest request = new Approval.ProcessWorkitemRequest();
request.setWorkitemId(workItemId);
request.setAction('Reject');
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (!result.isSuccess()) {
throw new ApprovalException('Rejection failed: ' + result.getErrors());
}
}
// Pending work items for current user
List<ProcessInstanceWorkitem> pendingItems = [
SELECT Id, ProcessInstance.TargetObjectId, ProcessInstance.Status,
ProcessInstance.TargetObject.Name, CreatedDate
FROM ProcessInstanceWorkitem
WHERE ActorId = :UserInfo.getUserId()
ORDER BY CreatedDate DESC
];
// Approval history for a record
List<ProcessInstanceStep> history = [
SELECT StepStatus, Comments, Actor.Name, CreatedDate
FROM ProcessInstanceStep
WHERE ProcessInstance.TargetObjectId = :recordId
ORDER BY CreatedDate ASC
];
// Active work item for a specific record
ProcessInstanceWorkitem workItem = [
SELECT Id FROM ProcessInstanceWorkitem
WHERE ProcessInstance.TargetObjectId = :recordId
AND ProcessInstance.Status = 'Pending'
LIMIT 1
];
Use the Submit for Approval action element: specify the record ID, optionally the approval process name, submitter, and comments.
Screen Flows can include an Integrated Approval component for in-flow approve/reject:
Screen Flow:
1. Show Record Details
2. Integrated Approval Component (history, Approve/Reject buttons, comments)
3. Decision: Check outcome
4. Update Records based on outcome
Autolaunched Flow (invoked by Approval Process):
1. Get Records: record's Region__c and Amount__c
2. Decision: Route by amount and region
- > 500K → VP Finance
- > 100K → Regional Manager
- Otherwise → Direct Manager
3. Return approver User ID
public static List<Map<String, Object>> getApprovalHistory(Id recordId) {
List<Map<String, Object>> trail = new List<Map<String, Object>>();
for (ProcessInstance pi : [
SELECT Id, Status, CreatedDate,
(SELECT StepStatus, Comments, Actor.Name, CreatedDate
FROM StepsAndWorkitems ORDER BY CreatedDate)
FROM ProcessInstance
WHERE TargetObjectId = :recordId
ORDER BY CreatedDate DESC
]) {
for (ProcessInstanceHistory step : pi.StepsAndWorkitems) {
trail.add(new Map<String, Object>{
'status' => step.StepStatus,
'approver' => step.Actor.Name,
'comments' => step.Comments,
'date' => step.CreatedDate
});
}
}
return trail;
}
These tests require an approval process to be deployed in the org's metadata. Approval processes cannot be created programmatically in tests.
@isTest
static void testApprovalSubmission() {
Account testAccount = new Account(Name = 'Test Account', AnnualRevenue = 50000);
insert testAccount;
Test.startTest();
Approval.ProcessSubmitRequest request = new Approval.ProcessSubmitRequest();
request.setObjectId(testAccount.Id);
request.setSubmitterId(UserInfo.getUserId());
Approval.ProcessResult result = Approval.process(request);
Test.stopTest();
System.assert(result.isSuccess(), 'Approval submission should succeed');
System.assertEquals('Pending', result.getInstanceStatus());
}
@isTest
static void testApprovalProcess() {
Account testAccount = new Account(Name = 'Test', AnnualRevenue = 50000);
insert testAccount;
Approval.ProcessSubmitRequest submitReq = new Approval.ProcessSubmitRequest();
submitReq.setObjectId(testAccount.Id);
Approval.ProcessResult submitResult = Approval.process(submitReq);
Id workItemId = submitResult.getNewWorkitemIds()[0];
Approval.ProcessWorkitemRequest approveReq = new Approval.ProcessWorkitemRequest();
approveReq.setWorkitemId(workItemId);
approveReq.setAction('Approve');
approveReq.setComments('Looks good');
Test.startTest();
Approval.ProcessResult approveResult = Approval.process(approveReq);
Test.stopTest();
System.assert(approveResult.isSuccess());
System.assertEquals('Approved', approveResult.getInstanceStatus());
}
approvalProcesses/
Discount_Approval.approvalProcess-meta.xml
Include in package.xml:
<types>
<members>Opportunity.Discount_Approval</members>
<name>ApprovalProcess</name>
</types>
Approval processes reference users, queues, and email templates. Ensure dependencies exist in the target org before deployment.
sf-apex-constraints — Governor limits and Apex safety rulesdevelopment
Update Salesforce platform reference docs with latest release features and deprecation announcements. Use when SessionStart hook warns docs are outdated or a new Salesforce release has shipped. Do NOT use for Apex or LWC development.
development
Use when syncing documentation after Salesforce Apex code changes. Update README, API docs, and deploy metadata references to match the current org codebase.
development
Use when managing context during long Salesforce Apex development sessions. Suggests manual compaction at logical intervals to preserve deploy and org context across phases.
tools
Visualforce development — pages, controllers, extensions, ViewState, JS Remoting, LWC migration. Use when maintaining VF pages, building PDFs, or planning VF-to-LWC migration. Do NOT use for LWC, Aura, or Flow.