13.3 Apex Actions for Flow, LWC, and Agentforce
Key Takeaways
- The User Interface objective requires implementing Apex that works with page components including Lightning Web Components, Flow, and Agentforce, which are three consumers reached through two annotations
- @AuraEnabled exposes Apex to LWC and Aura, while @InvocableMethod exposes the same class to Flow and to Agentforce Builder as a custom agent action
- An invocable method must be public or global and static, take exactly one List parameter, and return void or a List; only one method per Apex class may carry @InvocableMethod
- Complex inputs and outputs use inner wrapper classes whose fields are annotated @InvocableVariable, because multi-argument signatures are not allowed
- Agentforce reads the label and description on @InvocableMethod and @InvocableVariable to choose the action and fill its parameters, so vague description text is a functional defect
13.3 Apex Actions for Flow, LWC, and Agentforce
Quick Answer: Apex reaches declarative and AI surfaces through annotations.
@AuraEnabledexposes a method to LWC and Aura.@InvocableMethodexposes it to Flow and makes it selectable in Agentforce Builder as a custom agent action. An invocable method must be public/global, static, take aList, and return aList, and only one method per class may carry the annotation. Complex inputs and outputs use wrapper classes whose fields are marked@InvocableVariable. Yourlabelanddescriptiontext is not decoration—Agentforce's reasoning engine reads it to decide when to call your action.
The User Interface domain objective reads: "Implement Apex to work with various types of page components, including Lightning Web Components, Flow, and Agentforce." Section 12.4 covered the LWC half in depth. This section closes the other two and shows why they share one mechanism.
Three Bridges Out of Apex
| Consumer | Annotation | Shape |
|---|---|---|
| LWC / Aura | @AuraEnabled (add cacheable=true for @wire reads) | Ordinary signature; the value returns to JavaScript |
| Flow (screen, record-triggered, scheduled) | @InvocableMethod | List in, List out |
| Agentforce custom agent action | @InvocableMethod — the same method, surfaced in Agentforce Builder | List in, List out |
The important insight for the exam: Flow and Agentforce share one annotation. There is no separate "agent" annotation to learn. Publish an invocable method and it becomes available both on the Flow canvas and in the agent-action catalog.
The Invocable Method Contract
public with sharing class EscalateCaseAction {
public class Request {
@InvocableVariable(label='Case ID' description='The Case to escalate' required=true)
public Id caseId;
@InvocableVariable(label='Reason' description='Why the case is being escalated')
public String reason;
}
public class Result {
@InvocableVariable(label='Escalated' description='True when the case was escalated')
public Boolean escalated;
@InvocableVariable(label='Message' description='Human-readable outcome for the user or agent')
public String message;
}
@InvocableMethod(
label='Escalate Case'
description='Escalates a support case and records the escalation reason on the case record.'
category='Support'
)
public static List<Result> escalate(List<Request> requests) {
Set<Id> caseIds = new Set<Id>();
for (Request r : requests) {
caseIds.add(r.caseId);
}
Map<Id, Case> cases = new Map<Id, Case>(
[SELECT Id, Status, Escalation_Reason__c FROM Case WHERE Id IN :caseIds]
);
List<Result> results = new List<Result>();
List<Case> toUpdate = new List<Case>();
for (Request r : requests) {
Case c = cases.get(r.caseId);
Result res = new Result();
if (c == null) {
res.escalated = false;
res.message = 'Case not found or not accessible.';
} else {
c.Status = 'Escalated';
c.Escalation_Reason__c = r.reason;
toUpdate.add(c);
res.escalated = true;
res.message = 'Case escalated.';
}
results.add(res);
}
if (!toUpdate.isEmpty()) {
update toUpdate;
}
return results;
}
}
Rules the exam tests:
| Rule | Detail |
|---|---|
| Access modifier | public or global |
| Static | Instance methods cannot be invocable |
| One per class | Only one method in a class may carry @InvocableMethod |
| Parameter | Exactly one parameter, and it must be a List |
| Return | void or a List; a returned list aligns positionally with the input list |
| Complex data | Use an inner class with @InvocableVariable fields; a multi-argument signature is not allowed |
| Callouts | Add callout=true to the annotation when the method performs an HTTP callout |
Note the bulk signature. Even when Agentforce sends a single request per conversational turn, and even when a screen Flow calls it once, the platform hands you a list—because a record-triggered Flow processing 200 records will. The bulkification discipline from Chapter 8 applies unchanged: collect ids, query once, DML once. The example above does exactly that, and it is the difference between an action that survives a data load and one that throws a LimitException.
Why label and description Matter Far More for Agentforce
For Flow, label and description are convenience text an admin reads on the canvas. For Agentforce, they are functional. The agent's reasoning engine reads the action's label, its description, and the label and description on every @InvocableVariable to decide:
- Whether this action is the right one for what the user asked
- Which value to put in each input parameter
Consequences you should be able to state on an exam item:
- A description of
"Handles cases"gives the agent nothing to discriminate on—it may fire your action for unrelated requests, or skip it when it should run. - An input labelled
"input1"with no description leaves the agent guessing what to pass into it. - Two actions with near-identical descriptions make selection unreliable.
Rule: write the description as if for a competent colleague who cannot read the method body—state what the action does, what it needs, and what it returns. Keep the text in the Apex annotation and in the action configuration in sync, because drift between them is a real maintenance defect.
Security Does Not Change at the Boundary
An invocable method is an entry point, and entry points are exactly where the Chapter 10 rules bite:
- Declare
with sharingon a class that an agent or a screen Flow calls on behalf of a user. Apex runs in system mode for CRUD and FLS by default, and an agent action is not a privileged context that exempts you. - Enforce field access with
USER_MODE,WITH SECURITY_ENFORCED, orSecurity.stripInaccessiblewhen the running user's permissions should govern the result. - Never concatenate an agent-supplied or Flow-supplied string into
Database.query—bind it. Input reaching an agent action is user-influenced text and must be treated as untrusted. - Return a safe message, not a raw exception carrying internal details, because the agent may surface your string to the end user verbatim.
Choosing the Bridge
| Requirement | Answer |
|---|---|
| A Lightning Web Component needs server data on load | @AuraEnabled(cacheable=true) with @wire |
| An LWC button performs a write | @AuraEnabled imperative call |
| An admin must reorder the logic without a developer | Put the Apex behind @InvocableMethod and let Flow orchestrate |
| A conversational agent must take a real action on a record | @InvocableMethod published as a custom agent action |
| The same logic must serve a screen Flow and an agent | One invocable method serves both |
| The logic must run on every save regardless of UI | A trigger, not an invocable action |
Exam Checklist
@AuraEnabledgoes to LWC and Aura;@InvocableMethodgoes to Flow and Agentforce- public/global, static, one per class,
Listin /Listout - Complex parameters use inner classes with
@InvocableVariable callout=truewhen the action performs an HTTP callout- Descriptions drive agent action selection—vague text is a functional defect, not a style nit
- Sharing, CRUD/FLS, and bind variables apply at the entry point like anywhere else
Bottom line: one annotation, @InvocableMethod, is how Apex becomes something an admin can drag onto a Flow canvas and something an Agentforce agent can decide to call on its own. Get the method shape right, write descriptions that actually describe, and keep the entry point bulk-safe and secure.
A developer needs one Apex method that a screen Flow can call and that also appears as a custom action in Agentforce Builder. What is the correct approach?
An Apex agent action is defined with the description "Handles records" and an input variable labelled "input1" with no description. The agent calls the action at the wrong times and passes the wrong values. What is the root cause?
Which signature is valid for a method intended to be exposed to Flow and Agentforce?