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:

EventPurpose
MATCHGROUP_MATCHDecide if the incoming record matches the MatchGroup, or defer to normal rules.
MATCHGROUP_ASSIGNEE_FILTERRecalculate or filter the available assignees for the matched record; return the list that Plauti Assign should use going forward.
MATCHGROUP_ASSIGNDecide 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.MatchGroupMatchInput with matchGroup and objectToMatch.
  • Return DTO: srrPluginModel.MatchGroupMatchOutput with one of MATCH, NO_MATCH, RUN_RULES in matchResult.
  • Null-safety: guard with SObject.isSet('LastName') before calling .get; verify non-null before String.valueOf.
Step 3 — Handle MATCHGROUP_ASSIGNEE_FILTER
  • Input DTO: srrPluginModel.AssigneeFilterInput with matchGroup, assigneeList, objectToAssign.
  • Return DTO: srrPluginModel.AssigneeFilterOutput with a filtered assigneeList that 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') and SObject.isSet('OwnerId') before .get; check for nulls before String.valueOf or casts.
Step 4 — Handle MATCHGROUP_ASSIGN
  • Input DTO: srrPluginModel.MatchGroupAssignInput with matchGroup, assigneeList (possibly filtered by your plugin), objectToAssign.
  • Return DTO: srrPluginModel.MatchGroupAssignOutput with one of ASSIGNED, QUEUE, RUN_ROUNDROBIN, RUN_LOAD_BALANCED.
  • If you return ASSIGNED, the chosen Assignee__c must exist in or be added to the list.
  • Null-safety: guard with SObject.isSet('LastName') before .get; verify non-null before String.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

EnumMeaning
MatchResult: MATCHRecord instantly matches this MatchGroup.
MatchResult: NO_MATCHRecord bypasses this MatchGroup.
MatchResult: RUN_RULESPlauti Assign evaluates the group’s MatchRules to decide.
AssignResult: ASSIGNEDAssign to the specific Assignee__c supplied by the plugin.
AssignResult: QUEUEAssign to the MatchGroup’s Queue User.
AssignResult: RUN_ROUNDROBINAssign to the next assignee in round-robin order.
AssignResult: RUN_LOAD_BALANCEDAssign 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.