In Salesforce, Sometime we need to automate processes like creating records, sending emails, or updating data — not just through buttons and triggers, but from tools like Flows, Prompt Builder, or even external integrations.
This is where Invocable Methods become super useful. In this blog post, we’ll take a deep dive into Invocable Methods in Apex — how to create them, how they work, their limitations and considerations, along with a real-world example.
Invocable methods are called natively from Rest, Apex, Flow, Agentforce agents or Einstein bots that interacts with the external API source. Invocable methods have dynamic input and output values and support describe calls.
Table of Contents
ToggleLimits and Considerations of Invocable Methods
- There can be at most one input parameter
- Always Takes a List as Input and Returns a List
- Have similar governor limits as other apex classes
- Can’t be used in SOQL queries, DML operations, or approval processes.
Business Scenario of Invocable Methods – Create Order with Products
Let’s take an example: we want to automatically create an Order and add multiple Products to it using Flow/Agentforce actions.
This involves a lot of logic:
- Create Order record
- Fetch product by product code
- Fetch pricebook entry
- Add Order Items (Order Line Items)
- Return the Order Number
we use an Invocable Methods.
global with sharing class OrderCreatorwithProducts {
@InvocableMethod(label='Create Order with Products' description='Creates an Order and adds Order Line Items using product codes and quantities.')
public static List<OrderCreationResult> createOrders(List<OrderCreationInput> inputs) {
List<Order> ordersToInsert = new List<Order>();
List<OrderItem> orderItemsToInsert = new List<OrderItem>();
List<OrderCreationResult> results = new List<OrderCreationResult>();
Id fixedAccountId = '001gK00000381oPQAQ'; // Hardcoded Account for demo
// Step 1: Fetch Standard Pricebook
Pricebook2 standardPricebook = [SELECT Id FROM Pricebook2 WHERE IsStandard = true LIMIT 1];
// Step 2: Create Orders
for (OrderCreationInput input : inputs) {
Order newOrder = new Order();
newOrder.AccountId = fixedAccountId;
newOrder.EffectiveDate = Date.today();
newOrder.Status = 'Draft';
newOrder.Name = 'Auto Order - ' + DateTime.now().format();
newOrder.Pricebook2Id = standardPricebook.Id;
ordersToInsert.add(newOrder);
}
insert ordersToInsert;
// Step 3: Create Order Items
Integer index = 0;
for (OrderCreationInput input : inputs) {
Order createdOrder = ordersToInsert[index];
index++;
for (Integer i = 0; i < input.productCodes.size(); i++) {
String code = input.productCodes[i];
Decimal qty = input.quantities[i];
// Get Product and Pricebook Entry
Product2 prod = [SELECT Id FROM Product2 WHERE ProductCode = :code AND IsActive = true LIMIT 1];
PricebookEntry pbe = [
SELECT Id, UnitPrice
FROM PricebookEntry
WHERE Product2Id = :prod.Id AND Pricebook2Id = :standardPricebook.Id
LIMIT 1
];
// Add Order Item
OrderItem oi = new OrderItem();
oi.OrderId = createdOrder.Id;
oi.PricebookEntryId = pbe.Id;
oi.Quantity = qty;
oi.UnitPrice = pbe.UnitPrice;
orderItemsToInsert.add(oi);
}
}
insert orderItemsToInsert;
// Step 4: Return Order Number
List<Id> orderIds = new List<Id>();
for (Order o : ordersToInsert) {
orderIds.add(o.Id);
}
Map<Id, Order> orderMap = new Map<Id, Order>(
[SELECT Id, OrderNumber FROM Order WHERE Id IN :orderIds]
);
for (Order o : ordersToInsert) {
OrderCreationResult result = new OrderCreationResult();
result.orderNumber = orderMap.get(o.Id).OrderNumber;
results.add(result);
}
return results;
}
// Input Wrapper Class
global class OrderCreationInput {
@InvocableVariable(label='Product Codes' description='List of Product Codes to add to the Order.')
public List<String> productCodes;
@InvocableVariable(label='Quantities' description='List of quantities corresponding to each product code.')
public List<Decimal> quantities;
}
// Output Wrapper Class
global class OrderCreationResult {
@InvocableVariable(label='Order Number' description='The Order Number of the created Order.')
public String orderNumber;
}
}
Simple Explanation of Code
@
InvocableMethod
– Makes thecreateOrders
method available to Flow or Agentforce Actions.(to make apex methods invoacble methods a method must be annotated with the @InvocableMethod
.- -@InvocableVariable– annotation identifies a class variable used as an input or output parameter for an InvocableMethod method’s invocable action
- OrderCreationInput – Input class to pass Product Codes and Quantities from Flow or Agentforce Actions.
- OrderCreationResult – Output class to return Order Number to Flow or Agentforce Actions.
- Fetch Pricebook – Gets Standard Pricebook for Order
- Create Orders – Loops through inputs and creates Orders
- Create Order Items – Finds product using code, gets PricebookEntry, and creates Order Items.
- Return Order Number – Sends back Order Number as output
For a correct bulkification implementation, the Inputs and Outputs must match on both the size and the order. For example, the i-th Output entry must correspond to the i-th Input entry. Matching entries are required for data correctness when your action is in bulkified execution, such as when an apex action is used in a record trigger flow.
Conclusion
Invocable methods make it easy to connect your custom Apex code with tools like Flows , Agentforce Actions and Prompt Builder. They help you build smart and powerful automation without writing everything in code. By using simple input and output classes, following best practices, and writing clean logic, you can create solutions that are easy to use, reusable, and perfect for both admins and developers. It’s a great way to get the best of both worlds — clicks and code working together.
You can deepen your understanding of Data Cloud by exploring our comprehensive course on Salesforce Data Cloud and Agentforce – Building Agents and also check out our latest blogs on our websites for additional knowledge about salesforce.