Routing Plugin
Operational Mode: Plugin
This article explains how to extend Plauti Assign matching and assignment by switching a MatchGroup to "Operational Mode: Plugin" and implementing a custom Apex class that follows global interface srrPluginInterface.
Overview
A plugin lets you override the standard MatchRules and AssigneeRules executed by Plauti Assign. When a MatchGroup is set to Plugin mode, Plauti Assign emits the next events that your class may handle:
| Event | Purpose |
|---|---|
| MATCHGROUP_MATCH | Decide if the incoming record matches the MatchGroup, or defer to normal rules. |
| MATCHGROUP_ASSIGNEE_FILTER | Recalculate or filter the available assignees for the matched record; return the list that Plauti Assign should use going forward. |
| MATCHGROUP_ASSIGN | Decide how a matched record is assigned (specific user, queue, round-robin, load-balance). |
isAvailable(eventType) is invoked first; return true for the events your plugin supports.
MATCHGROUP_ASSIGNEE_FILTER
The plugin is used to recalculate the available assignees of a match group based on the customer's business rules. The plugin receives the record that is currently being assigned, the match group that has been matched to the record, and the list of available assignees, according to the business rules configured in Plauti Assign. The plugin returns the list of assignees that Plauti Assign should use to run the assignment logic specified via the match group's operational mode.
Implementation
Core interface & example
The sample plugin checks the record’s LastName: it flags “Carpenter” as a direct match, “Meadow” as no-match, and “Gilmore” as “run rules,” otherwise it does nothing. If the record is a match, it:
- Filters the assignee list so “Carpenter” records only consider the user with alias jos, blocks “Meadow” by returning an empty list, and leaves “Gilmore” unchanged. Other surnames exclude the record’s current owner.
- Assigns “Carpenter” records to the user with alias jos, throws an exception for “Meadow,” routes “Gilmore” to the MatchGroup’s queue user, and load-balances any other surname.
global class MySrrPlugin implements srrPluginInterface {
// Define which events this plugin supports
private static final Set<String> SUPPORTED = new Set<String>{
'MATCHGROUP_MATCH', 'MATCHGROUP_ASSIGNEE_FILTER', 'MATCHGROUP_ASSIGN'
};
// isAvailable is called by the framework to check if we handle a given event
public Boolean isAvailable(srrPlugin.PluginEventType evt) {
return SUPPORTED.contains(evt.name());
}
// execute dispatches incoming events to the appropriate handler
public Object execute(srrPlugin.PluginEventType evt, Object data) {
switch on evt {
when MATCHGROUP_MATCH {
return match((srrPluginModel.MatchGroupMatchInput) data);
}
when MATCHGROUP_ASSIGNEE_FILTER {
return assigneeFilter((srrPluginModel.AssigneeFilterInput) data);
}
when MATCHGROUP_ASSIGN {
return assign((srrPluginModel.MatchGroupAssignInput) data);
}
when else {
return null;
}
}
}
/* ---------- Matching logic ---------- */
private srrPluginModel.MatchGroupMatchOutput match(
srrPluginModel.MatchGroupMatchInput in) {
// Prepare output container
srrPluginModel.MatchGroupMatchOutput out =
new srrPluginModel.MatchGroupMatchOutput();
// Null-safe fetch of LastName using SObject.isSet
String lastName;
if (in != null
&& in.objectToMatch != null
&& in.objectToMatch.isSet('LastName')
&& in.objectToMatch.get('LastName') != null) {
lastName = String.valueOf(in.objectToMatch.get('LastName'));
} else {
lastName = null;
}
// Custom matching rules based on LastName value
if (lastName == 'Carpenter') {
// Always treat 'Carpenter' as a match
out.matchResult = srrPluginModel.MatchResult.MATCH;
} else if (lastName == 'Meadow') {
// Always skip 'Meadow' (no match), continue to next MatchGroup
out.matchResult = srrPluginModel.MatchResult.NO_MATCH;
} else if (lastName == 'Gilmore') {
// Defer to the MatchGroup's own rules for 'Gilmore'
out.matchResult = srrPluginModel.MatchResult.RUN_RULES;
}
// Return the chosen MatchResult
return out;
}
/* ---------- Assignee filtering logic ---------- */
// Return the list of assignees Plauti Assign should use in subsequent assignment logic.
private srrPluginModel.AssigneeFilterOutput assigneeFilter(
srrPluginModel.AssigneeFilterInput in) {
srrPluginModel.AssigneeFilterOutput out =
new srrPluginModel.AssigneeFilterOutput();
out.assigneeList = new List<Assignee__c>();
String lastName;
if (in != null
&& in.objectToAssign != null
&& in.objectToAssign.isSet('LastName')
&& in.objectToAssign.get('LastName') != null) {
lastName = String.valueOf(in.objectToAssign.get('LastName'));
} else {
lastName = null;
}
if (lastName == 'Carpenter') {
// Restrict to just the user with alias 'jos'
User u = [SELECT Id FROM User WHERE Alias = 'jos' LIMIT 1];
if (in != null && in.assigneeList != null) {
for (Assignee__c a : in.assigneeList) {
// Guard dynamic get with isSet
if (a != null && a.isSet('User__c') && (Id)a.get('User__c') == u.Id) {
out.assigneeList.add(a);
break; // keep only the 'jos' assignee
}
}
}
} else if (lastName == 'Meadow') {
// Return an empty list to prevent assignment with the current MatchGroup
return out;
} else if (lastName == 'Gilmore') {
// Do not change the list; allow normal group logic to proceed
out.assigneeList = in.assigneeList;
return out;
} else {
// Exclude the record's current owner from consideration (null-safe with isSet)
Id ownerId = null;
if (in != null
&& in.objectToAssign != null
&& in.objectToAssign.isSet('OwnerId')
&& in.objectToAssign.get('OwnerId') != null) {
ownerId = (Id) in.objectToAssign.get('OwnerId');
}
if (in != null && in.assigneeList != null) {
for (Assignee__c a : in.assigneeList) {
if (a == null) continue;
Id assigneeUserId = null;
if (a.isSet('User__c') && a.get('User__c') != null) {
assigneeUserId = (Id) a.get('User__c');
}
if (ownerId == null || assigneeUserId != ownerId) {
out.assigneeList.add(a);
}
}
}
}
return out;
}
/* ---------- Assignment logic ---------- */
private srrPluginModel.MatchGroupAssignOutput assign(
srrPluginModel.MatchGroupAssignInput in) {
srrPluginModel.MatchGroupAssignOutput out =
new srrPluginModel.MatchGroupAssignOutput();
String lastName;
if (in != null
&& in.objectToAssign != null
&& in.objectToAssign.isSet('LastName')
&& in.objectToAssign.get('LastName') != null) {
lastName = String.valueOf(in.objectToAssign.get('LastName'));
} else {
lastName = null;
}
if (lastName == 'Carpenter') {
// Query the User with alias 'jos'
User u = [SELECT Id FROM User WHERE Alias='jos' LIMIT 1];
// Find the corresponding Assignee__c in the provided list
if (in != null && in.assigneeList != null) {
for (Assignee__c a : in.assigneeList) {
if (a != null && a.isSet('User__c') && a.get('User__c') != null
&& (Id)a.get('User__c') == u.Id) {
out.assignee = a;
break;
}
}
}
if (out.assignee == null) {
// Either add an Assignee__c to the list or throw, based on your policy
throw new srrPlugin.PluginException(
'Required Assignee__c for user alias "jos" was not found in assigneeList.');
}
// Instruct SRR to assign to that specific user
out.assignResult = srrPluginModel.AssignResult.ASSIGNED;
} else if (lastName == 'Meadow') {
// Throw an exception if trying to assign a record we declared NO_MATCH
throw new srrPlugin.PluginException(
'Plugin declared NO_MATCH or returned no assignees – assignment prohibited.');
} else if (lastName == 'Gilmore') {
// Send 'Gilmore' records to the MatchGroup’s Queue User
out.assignResult = srrPluginModel.AssignResult.QUEUE;
} else {
// All other matched records are load-balanced across assignees
out.assignResult = srrPluginModel.AssignResult.RUN_LOAD_BALANCED;
}
// Return the chosen AssignResult (and assignee if applicable)
return out;
}
}Step 1 — Create the Apex Class
Create a global class (visibility is required so Type.forName can instantiate it) that implements srrPluginInterface.
Step 2 — Handle MATCHGROUP_MATCH
- Input DTO:
srrPluginModel.MatchGroupMatchInputwith matchGroup and objectToMatch. - Return DTO:
srrPluginModel.MatchGroupMatchOutputwith one ofMATCH,NO_MATCH,RUN_RULESinmatchResult. - Null-safety: guard with
SObject.isSet('LastName')before calling.get; verify non-null beforeString.valueOf.
Step 3 — Handle MATCHGROUP_ASSIGNEE_FILTER
- Input DTO:
srrPluginModel.AssigneeFilterInputwith matchGroup, assigneeList, objectToAssign. - Return DTO:
srrPluginModel.AssigneeFilterOutputwith a filteredassigneeListthat Plauti Assign will use for subsequent assignment logic. - Use this to apply custom business rules that narrow or reorder the available assignees for the matched record.
- If you return an empty list, downstream behavior depends on the MatchGroup’s operational mode and configuration.
- Null-safety: use
SObject.isSet('LastName')andSObject.isSet('OwnerId')before.get; check for nulls beforeString.valueOfor casts.
Step 4 — Handle MATCHGROUP_ASSIGN
- Input DTO:
srrPluginModel.MatchGroupAssignInputwith matchGroup, assigneeList (possibly filtered by your plugin), objectToAssign. - Return DTO:
srrPluginModel.MatchGroupAssignOutputwith one ofASSIGNED,QUEUE,RUN_ROUNDROBIN,RUN_LOAD_BALANCED. - If you return
ASSIGNED, the chosenAssignee__cmust exist in or be added to the list. - Null-safety: guard with
SObject.isSet('LastName')before.get; verify non-null beforeString.valueOf.
Step 5 — Register the Plugin
In Setup › Custom Metadata Types › SRR Options (leadassist__SRR_Options__mdt) edit the record with Developer Name = Plugin_Class_Name and set Value to the fully-qualified class name (e.g. MySrrPlugin).
Step 6 — Enable Plugin Mode on the MatchGroup
Open the MatchGroup, edit Operational Mode, choose Plugin, and save.
Supported Results
| Enum | Meaning |
|---|---|
MatchResult: MATCH | Record instantly matches this MatchGroup. |
MatchResult: NO_MATCH | Record bypasses this MatchGroup. |
MatchResult: RUN_RULES | Plauti Assign evaluates the group’s MatchRules to decide. |
AssignResult: ASSIGNED | Assign to the specific Assignee__c supplied by the plugin. |
AssignResult: QUEUE | Assign to the MatchGroup’s Queue User. |
AssignResult: RUN_ROUNDROBIN | Assign to the next assignee in round-robin order. |
AssignResult: RUN_LOAD_BALANCED | Assign to the least-loaded assignee in the group. |
For MATCHGROUP_ASSIGNEE_FILTER, the plugin returns srrPluginModel.AssigneeFilterOutput containing a List of type: Assignee__c that Plauti Assign uses as the candidate set for subsequent assignment logic.
flowchart TD
A["Record Received"] --> B{"MatchGroup in Plugin Mode?"}
B -- "No" --> C["Standard Plauti Assign Process"] --> Z["End"]
B -- "Yes" --> D["MATCHGROUP_MATCH (plugin)"]
D --> E{"MatchResult"}
E -- "MATCH" --> MAF["MATCHGROUP_ASSIGNEE_FILTER (plugin)"]
E -- "NO_MATCH" --> I["End (No Match)"]
E -- "RUN_RULES" --> F["Evaluate MatchRules"]
F --> G{"Matched after rules?"}
G -- "Yes" --> MAF
G -- "No" --> I
MAF --> H["MATCHGROUP_ASSIGN (plugin)"]
H --> J{"AssignResult"}
J -- "ASSIGNED" --> P1["Assign to specific assignee (plugin)"]
J -- "QUEUE" --> P2["Assign to MatchGroup queue user"]
J -- "RUN_ROUNDROBIN" --> P3["Assign next assignee (round-robin)"]
J -- "RUN_LOAD_BALANCED" --> P4["Assign least-loaded assignee"]
P1 --> P["Record Assigned"]
P2 --> P
P3 --> P
P4 --> P
The plugin receives full context DTOs so you can query additional data, log diagnostics, or dynamically build Assignee__c records as needed. Use MATCHGROUP_ASSIGNEE_FILTER to enforce business-specific eligibility and prioritization before the assignment algorithm runs. Ensure .get calls are guarded with SObject.isSet and null checks before String.valueOf.
Updated about 1 month ago