Building Invocable Methods for Lightning Flow Integration
The @InvocableMethod annotation exposes an Apex method as an action in Flow Builder, making complex logic available to admins without code. When designed well, invocable methods become reusable building blocks that bridge Apex power with declarative ease. When designed poorly, they become brittle single-use endpoints that frustrate everyone.
Method Signature Requirements
An invocable method has strict signature rules:
- Must be
public staticorglobal static - Must accept exactly one parameter: a
List<T>where T is a primitive, SObject, or a class with@InvocableVariableproperties - Must return
voidor aList<T>with the same type constraints - Only one
@InvocableMethodis allowed per class
public class AccountRatingCalculator {
@InvocableMethod(
label='Calculate Account Rating'
description='Evaluates account metrics and assigns a rating (Gold, Silver, Bronze).'
category='Account Management'
)
public static List<Result> calculate(List<Request> requests) {
List<Result> results = new List<Result>();
for (Request req : requests) {
Result res = new Result();
res.rating = deriveRating(req.annualRevenue, req.openOpportunities);
res.ratingReason = buildReason(req.annualRevenue, req.openOpportunities);
results.add(res);
}
return results;
}
private static String deriveRating(Decimal revenue, Integer openOpps) {
if (revenue > 1000000 && openOpps > 5) return 'Gold';
if (revenue > 500000 || openOpps > 3) return 'Silver';
return 'Bronze';
}
private static String buildReason(Decimal revenue, Integer opps) {
return 'Revenue: ' + revenue + ', Open Opportunities: ' + opps;
}
// --- Input wrapper ---
public class Request {
@InvocableVariable(label='Annual Revenue' required=true)
public Decimal annualRevenue;
@InvocableVariable(label='Open Opportunity Count' required=true)
public Integer openOpportunities;
@InvocableVariable(label='Account ID' required=true)
public Id accountId;
}
// --- Output wrapper ---
public class Result {
@InvocableVariable(label='Calculated Rating')
public String rating;
@InvocableVariable(label='Rating Reason')
public String ratingReason;
}
}
@InvocableVariable Details
Each property on the wrapper class gets its own annotation:
| Attribute | Purpose | Example |
|---|---|---|
label | Display name in Flow Builder | 'Annual Revenue' |
description | Tooltip help text | 'Total annual revenue in USD' |
required | Whether the Flow must populate it | true or false |
Properties must be public (not private with getters). Supported types include primitives (String, Integer, Decimal, Boolean, Date, Datetime, Id), SObject, and List<T> of these types.
Why Bulkification Matters
Flows typically call invocable methods with a single record per invocation. However, the platform may batch multiple Flow interviews into a single Apex invocation for performance. This is especially true in record-triggered flows where a bulk data load fires the flow for hundreds of records simultaneously.
Always process the full input list rather than assuming requests.size() == 1:
// Correct: processes the entire list
public static List<Result> calculate(List<Request> requests) {
List<Result> results = new List<Result>();
Set<Id> accountIds = new Set<Id>();
for (Request req : requests) {
accountIds.add(req.accountId);
}
// Single query for all accounts
Map<Id, Account> accountMap = new Map<Id, Account>(
[SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);
for (Request req : requests) {
Result res = new Result();
res.rating = deriveRating(req.annualRevenue, req.openOpportunities);
results.add(res);
}
return results;
}
If you write logic assuming a single input, a bulk operation will either hit governor limits or produce incorrect results.
Labels and Descriptions: Your Admin's Documentation
The label, description, and category attributes on @InvocableMethod are the only documentation Flow admins see in Flow Builder. Invest in clear, specific labels:
- label -- Action name in the palette (e.g., "Calculate Account Rating", not "DoCalc")
- description -- What it does and any prerequisites (e.g., "Evaluates account metrics and assigns a rating. Requires Annual Revenue and Open Opportunity Count.")
- category -- Groups the action in the palette (e.g., "Account Management")
Limitations and Workarounds
One method per class. If you need multiple invocable actions for the same domain, create separate classes. A common convention is [Domain][Action]Invocable (e.g., AccountRatingCalculator, AccountTerritoryAssigner).
No overloading. You cannot have multiple @InvocableMethod signatures in the same class.
Collections as inputs. To pass a list of records from a Flow, use List<SObject> or List<Id> as a property type on your request wrapper. Note that Flow collection variables map to List properties.
Return type alignment. The output list must have the same number of elements as the input list, in the same order. Each input element maps to its corresponding output element by index.
Key Takeaways
- Design invocable methods as reusable, well-labeled building blocks -- not one-off utilities.
- Always bulkify. The platform may batch multiple Flow interviews into one Apex call.
- Use descriptive
label,description, andcategoryvalues -- they are the admin-facing API documentation. - One
@InvocableMethodper class. Use separate classes for separate actions. - Return results in the same order and size as the input list.