本文へ移動
cccskills
無料GitHub で公開

approval-process-apex-patterns

Programmatically driving Salesforce Approval Processes from Apex — `Approval.process(ProcessSubmitRequest)` to submit, `ProcessWorkitemRequest` to approve / reject / reassign, recall semantics, querying `ProcessInstance` and `ProcessInstanceWorkitem` to find pending approvals, and the bulk-submit / bulk-action error-row handling. Covers when to use Apex-driven approval (system-initiated submission, batch approvals, custom UIs) vs leaving the platform's standard buttons in place. NOT for the Approval Process metadata definition itself (that's admin / declarative — see admin/approval-processes), NOT for Flow-based approvals (use flow/flow-orchestration-patterns).

インストール方法を見る

含まれるファイル(8)

  • SKILL.md32.1 KB
  • references/examples.md9.5 KB
  • references/gotchas.md20.5 KB
  • references/llm-anti-patterns.md9.1 KB
  • references/metadata-examples.md20.8 KB
  • references/well-architected.md7.6 KB
  • scripts/check_approval_process_apex_patterns.py20.1 KB
  • templates/approval-process-apex-patterns-template.md3.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Approval Process Apex Patterns

The platform provides standard approval-process buttons (Submit for Approval, Approve, Reject, Reassign) on record pages. They work for human-driven, single-record approvals. They don't cover:

  • Programmatic submission (a scheduled batch creates 1,000 records and submits them all for approval).
  • Programmatic action (a system event approves / rejects on behalf of a user — careful with this one).
  • Custom UI (a custom Lightning component that bundles submit + status display + approve buttons).
  • Querying pending items (which records are awaiting approval, by whom, for how long).

This skill covers the Apex API for those cases.

What this skill is NOT. Defining the Approval Process itself (entry criteria, approval steps, approver assignment) is declarative admin work — see admin/approval-processes. The modern Flow-based equivalent (Flow Orchestration with interactive steps assigned to approvers) is a different runtime entirely — see flow/flow-orchestration-patterns.


Before Starting

  • Confirm the Approval Process is defined and active. Apex references the process by developer name. Check it before you write anything else:

    SELECT Id, DeveloperName, Name, State, TableEnumOrId, LockType
    FROM ProcessDefinition
    WHERE DeveloperName = 'Expense_Approval'
    

    State must be Active (values: Active, Inactive, Obsolete — Object Reference, ProcessDefinition, object_reference.txt L225341-225426).

  • Confirm the submitter is an allowed submitter. "The user must be one of the allowed submitters in the process definition setup" (Apex Reference, setSubmitterId, apexrefguide.txt L4021-4024). Setting submitterId to a record owner who isn't in allowedSubmitters fails that row.

  • Decide the bulk shape. Approval.process(...) is overloaded for a single ProcessRequest or a List<ProcessRequest>, each with an optional allOrNone (apexrefguide.txt L200066-200172). Each call counts as one DML statement against the 150-statement limit, and the records it processes count against the 10,000 records-processed limit (apexdev.txt L19548-19556, L19624).

  • Decide the error-row policy. allOrNone = true (default): any failure rolls back the whole batch. allOrNone = false: "the remainder of the approval processes can still succeed" (apexrefguide.txt L200119-200123).


Questions to Ask Before Configuring

Ask these before the first new Approval.ProcessSubmitRequest(). Every one of them maps to a row that fails at runtime rather than at compile time.

AskWhy it mattersWhat a good answer adds
"Who is in allowedSubmitters on this process, and is the record owner one of them?"setSubmitterId rejects any user who isn't an allowed submitter, so a batch submitting on behalf of owners fails per rowThe list of owner profiles/roles to add to allowedSubmitters, or a decision to submit as a single service submitter
"Is allowRecall true, and who is allowed to recall?"setAction('Removed') is admin-only; allowRecall=false narrows recall to administrators entirelyWhether Pattern D can run in the editing user's context at all, or needs an admin-context escalation
"Does any step use approver type adhoc or queue?"adhoc needs an approver supplied at submission; a queue-assigned workitem has an ActorId pointing at a Group, not a UserWhich steps need setNextApproverIds, and whether the stuck-approval query can dereference Actor.IsActive at all
"Is this process the only active one on the object, and what is its process order?"With processDefinitionNameOrId null the platform walks every applicable process in org process order — and process order is not carried in the metadataA decision to always name the process explicitly, plus a post-deploy step to reset process order
"Is whenMultipleApprovers on any step Unanimous?"On a unanimous step, one rejection flips every other approver's StepStatus to NoResponse, not RejectedThe correct filter for the approval-audit report, so rejections aren't undercounted
"How many rows will one transaction submit, and is it sync or async?"Approval.process is DML: 150 statements and 10,000 records processed per transactionThe chunk size and whether this belongs in Batch Apex or Queueable rather than a trigger
"What happens to a row that fails — retry, park, or alert?"allOrNone=false returns per-row ProcessResults that nothing reads unless you write the handlerThe error-row destination: a log object, a retry queue, or an admin notification

What a proper implementation adds over just calling Approval.process(req): submissions land in the right process under the right submitter, failed rows are visible and re-drivable instead of silently rolled back, and the approval audit trail says what an auditor expects it to say.


Core Concepts

Request types

RequestPurpose
Approval.ProcessSubmitRequestSubmit a record into an approval process
Approval.ProcessWorkitemRequestTake action on a pending work item — approve / reject / reassign / remove

Both are passed to Approval.process(...). The call returns a list of Approval.ProcessResult (one per input Request) with success / errors.

ProcessSubmitRequest essentials

Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
req.setObjectId(record.Id);
req.setProcessDefinitionNameOrId('Expense_Approval_Process');  // approval process API name
req.setSubmitterId(UserInfo.getUserId());                       // who's "submitting"
req.setComments('Submitting via batch on month-end close');
req.setSkipEntryCriteria(false);                                 // run the entry criteria
Approval.ProcessResult result = Approval.process(req);

Key options:

MethodDocumented contract (Apex Reference Guide)
setProcessDefinitionNameOrId(nameOrId)"The process definition developer name or process definition ID." If null, "every entry criteria of the process definition in the process order is evaluated and the one that satisfies is picked" (apexrefguide.txt L3974-3989). Use the developer name — record IDs are not portable.
setSubmitterId(userID)"The user must be one of the allowed submitters in the process definition setup. If you don't set a submitter ID, the process uses the current user as the submitter" (apexrefguide.txt L4021-4024).
setSkipEntryCriteria(skipEntryCriteria)Skips entry-criteria evaluation for the named process. "If the process definition name or ID is not specified, this parameter is ignored" (apexrefguide.txt L4005-4014).
setComments(comments)Inherited from ProcessRequest. Lands in ProcessInstanceStep.Comments, limit 4,000 bytes (object_reference.txt L226666-226675).
setNextApproverIds(nextApproverIds)Inherited from ProcessRequest. Parameter "must be a single-entry list"; it "sets the ActorId field of the associated ProcessInstanceWorkItem" (apexrefguide.txt L3631-3634, L3684-3695).

setSkipEntryCriteria(true) submits a record the admin's criteria were written to exclude. Record why in the calling class, not in a commit message.

ProcessWorkitemRequest essentials

// Find the pending workitem.
ProcessInstanceWorkitem workitem = [
    SELECT Id FROM ProcessInstanceWorkitem
    WHERE ProcessInstance.TargetObjectId = :recordId
      AND ProcessInstance.Status = 'Pending'
    ORDER BY CreatedDate DESC LIMIT 1
];

Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
req.setWorkitemId(workitem.Id);
req.setAction('Approve');     // 'Approve', 'Reject', 'Removed' (recall), or null + setNextApproverIds for reassign
req.setComments('Approved by system per policy 4.2');
Approval.ProcessResult result = Approval.process(req);

Action values — "Valid values are: Approve, Reject, or Removed. Only system administrators can specify Removed" (apexrefguide.txt L4123-4127):

ValueEffectConstraint
'Approve'Approves the workitem—
'Reject'Rejects the workitemOn a Unanimous step, other approvers' steps flip to NoResponse
'Removed'Recalls the submissionSystem administrators only, per the Apex Reference. ApprovalProcess.allowRecall=false narrows recall to administrators even in the UI (api_meta.txt L23095-23098)
null + setNextApproverIdsReassignThe list must hold exactly one Id (apexrefguide.txt L3690-3695)

You rarely need a query to find the workitem you just created. ProcessResult.getNewWorkitemIds() returns "the IDs of the new items submitted to the approval process" (apexrefguide.txt L3795-3801), so a submit-then-approve sequence in the same transaction reads straight from the submit result — this is the shape the Apex Developer Guide's own sample uses (apexdev.txt L20651-20715).

Querying pending approvals

List<ProcessInstance> pending = [
    SELECT Id, TargetObjectId, Status, SubmittedById, ElapsedTimeInDays,
           (SELECT Id, ActorId FROM Workitems)
    FROM ProcessInstance
    WHERE Status = 'Pending'
      AND ProcessDefinition.DeveloperName = 'Expense_Approval'
];

Workitems is the child relationship name for ProcessInstanceWorkitem, and Steps for ProcessInstanceStep (object_reference.txt L226220-226240). Age on ElapsedTimeInDays, not CreatedDate — the field tables for these objects document the elapsed-time fields and do not list CreatedDate.

ProcessInstance is the in-flight approval. ProcessInstanceStep is the audit trail of completed steps. ProcessInstanceWorkitem is the open assignment to a specific approver.

ObjectSupported callsNotes from the Object Reference
ProcessDefinitionquery, retrieve, searchRead-only. DeveloperName, State, LockType (object_reference.txt L225355-225426)
ProcessInstancedescribeSObjects, query, retrieveStatus picklist: Approved, Fault, Held, NoResponse, Pending, Reassigned, Rejected, Removed, Started (L226112-226145). TargetObjectId is not nillable (L226147-226160)
ProcessInstanceStepdescribeSObjects, query, retrieveCompleted steps. StepStatus uses the same nine values (L226748-226766)
ProcessInstanceWorkitemdelete, describeSObjects, query, retrieve, updateOpen assignments. ActorId is Updateable, so reassignment is also a plain DML update (L226816-226835)
ProcessInstanceHistoryread-only view"Combines fields from ProcessInstanceStep and ProcessInstanceWorkitem" (L226802-226810)

ActorId on both ProcessInstanceStep and ProcessInstanceWorkitem is a polymorphic relationship field that "Refers To Group, User" (object_reference.txt L226648-226670, L226820-226835). A bare Actor.IsActive in the SELECT does not compile; use TYPEOF, and handle the queue case where there is no user at all:

SELECT Id, ActorId, ElapsedTimeInDays, ProcessInstance.TargetObjectId,
       TYPEOF Actor
           WHEN User THEN Username, IsActive
           WHEN Group THEN DeveloperName
       END
FROM ProcessInstanceWorkitem
WHERE ProcessInstance.Status = 'Pending'
  AND ElapsedTimeInDays > 7

ElapsedTimeInDays / InHours / InMinutes are filterable and sortable on both the workitem and the step (object_reference.txt L226836-226878), which is what the stuck-approval audit should age on.

Locking and unlocking

Approval.lock / Approval.unlock / Approval.isLocked set and read the approval lock independently of the process itself. Two constraints worth internalising: "Salesforce admins can edit locked records. Depending on your approval process configuration settings, an assigned approver can also edit locked records", and "Record locks and unlocks are treated as DML. They're blocked before a callout, they count toward your DML limits, and if a failure occurs, they're rolled back along with the rest of your transaction" (apexrefguide.txt L199726-199736). Both take the same optional allOrNothing flag and return LockResult / UnlockResult arrays positionally matched to the input.

Approval.unlock drops the lock. It does not remove the pending approval — the ProcessInstanceWorkitem survives and the approver can still act on it.

Bulk submission

The overload set is process(request), process(request, allOrNone), process(requests), process(requests, allOrNone) (apexrefguide.txt L200066-200172). The guides document no per-call cap on list size; what they do document is that each call is one DML statement (150 per transaction) and that the records it processes count against the 10,000 records-processed-per-transaction limit (apexdev.txt L19548-19556, L19624). UNVERIFIED (2026-09-05): the widely-repeated "200 requests per Approval.process call" figure does not appear in the Apex Reference Guide, the Apex Developer Guide, or the App Limits cheat sheet. Chunking at 200 is still a sound convention — it keeps heap and per-call failure blast radius predictable — but treat it as a design choice, not a documented governor.

Chunk the list anyway, and count DML statements while you do:

List<Approval.ProcessSubmitRequest> requests = ...;
for (Integer i = 0; i < requests.size(); i += 200) {
    Integer end = Math.min(i + 200, requests.size());
    List<Approval.ProcessSubmitRequest> chunk =
        new List<Approval.ProcessSubmitRequest>();
    for (Integer j = i; j < end; j++) chunk.add(requests[j]);
    Approval.ProcessResult[] results = Approval.process(chunk, false);  // allOrNone = false
    for (Integer j = 0; j < results.size(); j++) {
        if (!results[j].isSuccess()) {
            // Log + decide per row
        }
    }
}

allOrNone = false is essential when you have known-bad rows mixed in (e.g. records that don't match entry criteria — those will fail individually rather than killing the whole batch).


Common Patterns

Pattern A — System-initiated batch submission

When to use. Month-end close: identify all expense reports past threshold, submit them all into approval automatically.

public class MonthEndExpenseSubmitter {
    public static void submitOverThreshold() {
        List<Expense__c> toSubmit = [
            SELECT Id FROM Expense__c
            WHERE Status__c = 'Draft'
              AND Total_Amount__c >= 1000
              AND Submitted_Date__c = NULL
        ];

        List<Approval.ProcessSubmitRequest> requests = new List<Approval.ProcessSubmitRequest>();
        for (Expense__c e : toSubmit) {
            Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
            req.setObjectId(e.Id);
            req.setProcessDefinitionNameOrId('Expense_Approval');
            req.setSubmitterId(e.OwnerId);  // from the owner, not the running batch user
            req.setComments('Auto-submitted by month-end batch');
            requests.add(req);
        }

        // Bulk submit, allow partial success.
        for (Integer i = 0; i < requests.size(); i += 200) {
            Integer end = Math.min(i + 200, requests.size());
            List<Approval.ProcessSubmitRequest> chunk = new List<Approval.ProcessSubmitRequest>();
            for (Integer j = i; j < end; j++) chunk.add(requests[j]);
            Approval.ProcessResult[] results = Approval.process(chunk, false);
            for (Integer j = 0; j < results.size(); j++) {
                if (!results[j].isSuccess()) {
                    ApplicationLogger.warn(
                        'Submission failed: ' + requests[j].getObjectId() +
                        ' — ' + results[j].getErrors()
                    );
                }
            }
        }
    }
}

Key points: setSubmitterId to the owner (not the batch user), allOrNone = false, log failures rather than abort.

The submitter caveat that bites here: every OwnerId you pass must be an allowed submitter on the process, or that row fails (apexrefguide.txt L4021-4024). If owners are a moving population, either widen allowedSubmitters to <type>owner</type> / <type>allInternalUsers</type> (api_meta.txt L23228-23262) or drop setSubmitterId and submit as one designated service user, and say so in the approval comment.

Pattern B — Auto-approve based on system event

When to use. A downstream system signals approval (e.g. CFO office signs off in an external system; a Platform Event fires; Apex subscriber auto-approves the matching Salesforce expense).

trigger ExpenseApprovedEventSubscriber on Expense_Approved__e (after insert) {
    EventBus.TriggerContext ctx = EventBus.TriggerContext.currentContext();
    for (Expense_Approved__e e : Trigger.new) {
        try {
            ProcessInstanceWorkitem wi = findPendingWorkitem(e.Expense_Id__c);
            if (wi == null) {
                ctx.setResumeCheckpoint(e.ReplayId);
                continue;
            }
            Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
            req.setWorkitemId(wi.Id);
            req.setAction('Approve');
            req.setComments('Auto-approved by external CFO system; ref ' + e.External_Ref__c);
            Approval.process(req);
            ctx.setResumeCheckpoint(e.ReplayId);
        } catch (Exception ex) {
            ApplicationLogger.error('Auto-approve failed', ex);
            ctx.setResumeCheckpoint(e.ReplayId);
        }
    }
}

Caveat: auto-approval is a security-sensitive action. Audit who can publish the trigger event. The approval-process-step defines who's authorized to approve; programmatic auto-approval bypasses that policy.

Pattern C — Find stuck approvals

When to use. Operational monitoring — surface approvals waiting on inactive users, approvals older than SLA, approvals on deleted source records.

List<ProcessInstanceWorkitem> stuck = [
    SELECT Id, ActorId, ElapsedTimeInDays,
           TYPEOF Actor
               WHEN User THEN Username, IsActive
               WHEN Group THEN DeveloperName
           END,
           ProcessInstance.TargetObjectId, ProcessInstance.Status
    FROM ProcessInstanceWorkitem
    WHERE ProcessInstance.Status = 'Pending'
      AND ElapsedTimeInDays > 7
];

for (ProcessInstanceWorkitem w : stuck) {
    if (w.Actor instanceof User && !((User) w.Actor).IsActive) {
        // Assigned to a deactivated user — reassign.
    } else if (w.Actor instanceof Group) {
        // Assigned to a queue — nobody owns it personally; nudge the queue.
    }
}

Three corrections worth carrying: ActorId is polymorphic over Group and User, so Actor.IsActive needs TYPEOF and a runtime type check; age on ElapsedTimeInDays, which the Object Reference documents as filterable, rather than on CreatedDate, which the ProcessInstanceWorkitem field table does not list (object_reference.txt L226816-226905); and do not test ProcessInstance.TargetObjectId == null as a deleted-record signal — that field is not nillable (object_reference.txt L226147-226160), so it never comes back empty.

Run as a scheduled batch; surface results to admin dashboard or notification channel.

Pattern D — Recall a submission when the source record is invalidated

When to use. Source record changes mid-approval such that the approval should no longer proceed. Example: expense report's amount drops below the threshold that requires VP approval.

Approach. Trigger / flow on the source record detects the invalidating change. Find the pending workitem and recall.

public static void recallApproval(Id recordId, String reason) {
    ProcessInstanceWorkitem wi = [
        SELECT Id FROM ProcessInstanceWorkitem
        WHERE ProcessInstance.TargetObjectId = :recordId
          AND ProcessInstance.Status = 'Pending'
        LIMIT 1
    ];
    Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
    req.setWorkitemId(wi.Id);
    req.setAction('Removed');  // recall
    req.setComments('Auto-recalled: ' + reason);
    Approval.process(req);
}

Permission note, stated flatly by the Apex Reference: "Only system administrators can specify Removed" (apexrefguide.txt L4123-4127). ApprovalProcess.allowRecall governs the UI equivalent — false means "only administrators can recall approval requests" (api_meta.txt L23094-23097). A recall from an ordinary user's trigger context will fail, so route it: catch the failure and park the record for an admin queue, or run the recall from an async context that executes as a designated admin.

An alternative that needs no admin: reassign instead of recall. ProcessInstanceWorkitem supports update() and ActorId is Updateable (object_reference.txt L226816-226835), so moving the request to a different approver is plain DML.


Decision Guidance

SituationApproachReason
User clicks Submit for Approval on a record pageStandard platform buttonNo Apex needed
System submits 1000 records in a batchPattern A with allOrNone = falseBulk-submit pattern; partial success preserves successful submissions
External system signals approvalPattern B via Platform Event subscriberAsync, durable, decoupled
Find approvals stuck > 7 days on inactive usersPattern C with scheduled batchOperational monitoring
Source record changes invalidate the approvalPattern D recallDon't let an invalid approval complete
Backlog of records stuck pending (bad load, rule change, departed approver)Setup → Mass Transfer Approval Requests (remove or transfer); no ApexDeclarative bulk cleanup — Pattern C only surfaces them; Approval.unlock drops the lock but removes nothing
Custom Lightning component shows approval status + buttonsApex-driven submit + workitem actionsWrap Approval.process() in @AuraEnabled
Approval step's assignedApprover type is adhocSupply the approver with setNextApproverIds (single-entry list)adhoc means "the approver for the step must be selected manually" (api_meta.txt L23389-23392); the Apex guide constrains the list to one Id
Need to move a pending request to a different approverupdate the ProcessInstanceWorkitem.ActorIdThe field is Updateable and the object supports update() — no admin permission needed, unlike recall
Bulk approve as part of a system batchPattern B shape (find workitems → ProcessWorkitemRequest)Same DML budget: one statement per call, rows against the 10,000 records-processed limit
User wants to delegate approvals to another userStandard Delegated Approver field on User; no ApexPlatform handles delegation
Audit trail of who-approved-whatQuery ProcessInstanceStepComplete audit history per approval

Recommended Workflow

  1. Answer the ## Questions to Ask Before Configuring table into templates/approval-process-apex-patterns-template.md. The allowedSubmitters, allowRecall, and adhoc-approver answers decide whether Patterns A and D are even runnable in your context.
  2. Read the process definition, don't assume it. Run the ProcessDefinition query from ## Before Starting, then retrieve the approvalProcess metadata (references/metadata-examples.md § "Retrieve, lint, deploy") and read allowedSubmitters, allowRecall, recordEditability, and every step's assignedApprover and whenMultipleApprovers.
  3. Deploy the test fixture — the minimal ApprovalProcess XML in references/metadata-examples.md — into a scratch org, so the Apex has something deterministic to submit into. Full declarative design lives in admin/approval-processes, not here.
  4. Write the service class from references/metadata-examples.md § "The Apex service class": bulk submit with allOrNone=false and an error-row collector, workitem action via getNewWorkitemIds() or the TYPEOF query, and recall guarded by the admin-only constraint.
  5. Lint before you deploy: python3 skills/admin/approval-process-apex-patterns/scripts/check_approval_process_apex_patterns.py --manifest-dir force-app/main/default. It reads approvalProcesses/*.approvalProcess-meta.xml and every .cls / .trigger in the tree, and flags a process name in Apex that no deployed process defines.
  6. Write the tests from references/metadata-examples.md § "Test class skeleton": success, mixed-row partial success, the not-an-allowed-submitter failure, and the non-admin recall failure. Assert on ProcessResult.getInstanceStatus() and getErrors(), not just isSuccess().
  7. Walk references/gotchas.md and confirm each of the four correction gotchas (5, 7, 8, 11) does not apply to the code you just wrote.

Review Checklist

  • Approval process developer name (not record Id) is referenced in setProcessDefinitionNameOrId, and it is never left null when a specific process is intended.
  • setSubmitterId is set explicitly when running-user-as-submitter is wrong, and every user it can be set to is in the process's allowedSubmitters.
  • setNextApproverIds is passed a single-entry list, and only where a step actually needs it.
  • allOrNone = false for bulk submissions where partial success is acceptable.
  • Approval.ProcessWorkitemRequest finds the workitem via ProcessInstanceWorkitem query, not assumed.
  • Auto-approval pattern (Pattern B) has explicit security review — who can publish the trigger event.
  • Recall pattern (Pattern D) handles permission errors gracefully (some processes restrict recall to admins).
  • Stuck-approval monitoring (Pattern C) runs as a scheduled batch with results surfaced to admins.
  • Any query over ActorId uses TYPEOF and handles the Group (queue) branch.
  • Aging filters use ElapsedTimeInDays / InHours, not CreatedDate.
  • The transaction's Approval.process calls are counted against the 150-DML-statement budget alongside every other DML in the same transaction.
  • python3 scripts/check_approval_process_apex_patterns.py --manifest-dir <dx root> passes, or every finding is triaged.

Salesforce-Specific Gotchas

  1. setProcessDefinitionNameOrId accepts the API name; hardcoded record IDs break across orgs. Use API name. (See references/gotchas.md § 1.)
  2. Default allOrNone = true rolls back the whole batch on first failure. Bulk submissions need allOrNone = false to preserve successful records. (See references/gotchas.md § 2.)
  3. setSubmitterId defaults to the running user. System batches that don't set it explicitly produce approvals "submitted by" the batch service account, not the actual owner. (See references/gotchas.md § 3.)
  4. ProcessWorkitemRequest action values are case-sensitive strings — 'Approve' not 'approve'. (See references/gotchas.md § 4.)
  5. Recall ('Removed') is documented as system-administrator-only. Not "usually restricted" — the Apex Reference says only sysadmins can specify it. (See references/gotchas.md § 5.)
  6. Auto-approval bypasses the approval-process step's "Approver assignment" — the platform records the running user as the approver, not the configured one. Audit implications. (See references/gotchas.md § 6.)
  7. The real bulk ceiling is DML, not a 200-request cap. Approval.process burns a DML statement and its rows count toward the 10,000 records-processed limit. (See references/gotchas.md § 7.)
  8. setSubmitterId fails for any user not in allowedSubmitters — the batch-submits-as-owner pattern breaks the moment an owner falls outside that list. (See references/gotchas.md § 11.)
  9. ActorId is polymorphic over Group and User, so Actor.IsActive needs TYPEOF and a queue-assigned workitem has no user at all. (See references/gotchas.md § 13.)
  10. A null processDefinitionNameOrId routes by org process order, and process order is not carried in the metadata across a deploy. (See references/gotchas.md § 14.)

Output Artifacts

ArtifactDescription
Apex class implementing the chosen patternSubmit / action / monitor / recall
Bulk submission helperChunks 200-at-a-time with allOrNone = false + per-row error logging
Stuck-approval monitorScheduled batch query + admin notification
Test classCovers success, partial-success, recall, and the inactive-approver case

Reference Files

FileRead it when
references/metadata-examples.mdYou need the deployable test-fixture ApprovalProcess XML, the bulk-submit / action / recall service class, the pending-approvals SOQL, the test-class skeleton, the package.xml, or the retrieve/deploy/verify commands
references/gotchas.mdA submission fails per-row, a recall is refused, a stuck-approval query returns nothing or won't compile, or a process that worked in the sandbox picks a different process in production
references/examples.mdWalking a concrete failure end to end — the rolled-back batch, the hardcoded process Id, the auto-approval audit finding, the invalidated-record recall, the stuck-approval sweep
references/llm-anti-patterns.mdReviewing Apex an AI assistant wrote against the Approval namespace before you deploy it
references/well-architected.mdJustifying Apex-driven approval to an architecture review, or tracing the official source behind any claim in this skill
templates/approval-process-apex-patterns-template.mdWorkflow step 1 — capturing the process-definition answers before any Apex is written
scripts/check_approval_process_apex_patterns.pyWorkflow step 5, before every deploy that touches approval Apex or approval-process metadata

Related Skills

  • admin/approval-processes — declarative definition of the approval process this skill drives.
  • flow/flow-orchestration-patterns — modern multi-stage approval pattern in Flow; consider before reaching for Apex.
  • apex/apex-event-bus-subscriber — when system events drive approval actions (Pattern B).
  • apex/apex-mocking-and-stubs — for the test class that covers Approval.process() failure modes.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use this skill when writing test-first, behavior-driven acceptance criteria in Given/When/Then format for a Salesforce user story. Covers happy path, edge cases, negative paths, permission boundaries, and data-state preconditions so the AC block can drive UAT scripts and Apex test design downstream. Trigger keywords: given when then, gherkin, behavior driven AC, test first acceptance criteria, scenario outline, BDD acceptance criteria. NOT for the user-story format itself (use admin/user-story-writing-for-salesforce). NOT for UAT script writing (use admin/uat-test-case-design). NOT for Apex test method generation (use agents/test-class-generator). NOT for high-level UAT planning (use admin/uat-and-acceptance-criteria). More triggers: acceptance criteria record, ac_id, req_id on a criterion, artefact under test, negative case for a rule-type requirement, no-match fall-through criterion, oracle for a Then clause, test type apex flow manual, criteria linter, check_ac_format.py, --manifest-dir.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Task and Event objects: polymorphic WhatId/WhoId, Activity object model, ActivityHistory vs OpenActivity, activity timeline customization, bulk task creation, Einstein Activity Capture boundaries. NOT for turning on EAC or calendar sync — use admin/einstein-activity-capture-setup. NOT for Email-to-Case — use admin/case-management-setup. Covers ActivitiesSettings metadata, enableActivities, shared Task/Event custom fields and FLS, TaskStatus value sets, Shared Activities and TaskRelation/EventRelation, recurring and child activities, and partial-success bulk DML on Task.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Design Invocable Apex actions that return deterministic, agent-friendly errors instead of surfacing raw exceptions to the LLM. NOT for authoring the action itself — @InvocableMethod schema, labels, security context — use agentforce/custom-agent-actions-apex. NOT for the Apex tests that force each error branch — use agentforce/agent-action-unit-tests.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Use when designing how an Agentforce agent extracts structured input parameters (slots) from a user's natural-language utterance to invoke an action. Triggers: 'agent extract date from user', 'agent action argument extraction', 'agentforce slot filling', 'agent invocable input mapping', 'agent fails to fill required parameter'. NOT for action authoring (use agentforce/agent-actions) or for prompt-template variable binding (use agentforce/prompt-builder-templates).

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Apex test patterns for @InvocableMethod agent actions: per-reason-code branch coverage, bulk safety, callout mocking, deterministic assertions. NOT for testing whether the agent routes to the right topic or answers well — use agentforce/agent-testing-and-evaluation. NOT for designing the error envelope the tests assert on — use agentforce/agent-action-error-handling.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Designing or reviewing Agentforce actions: Flow actions, Apex invocable actions, prompt-template actions, action naming, input and output contracts, confirmation requirements, and safe error behavior. Triggers: 'agent actions', 'build an agent that can look up an order', 'make the agent able to check order status', 'give the agent a capability', 'agent that can retrieve a record', 'flow action for agent', 'agent invocable action', 'action schema design'. NOT for writing the Apex class itself — use agentforce/custom-agent-actions-apex. NOT for the reason_code error envelope that stops an agent retry loop — use agentforce/agent-action-error-handling.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

PranavNagrecha のスキルをすべて見る

このスキルの問題を報告する