name: omni-channel-custom-routing description: "Use this skill to implement Apex-driven custom routing logic for Omni-Channel work items using PendingServiceRouting and SkillRequirement objects. Trigger keywords: PendingServiceRouting, SkillRequirement, IsReadyForRouting, skills-based routing, custom routing Apex. NOT for declarative Omni-Channel queue-based routing setup, routing configurations in Setup UI, or Einstein Classification routing rules." category: apex salesforce-version: "Spring '25+" well-architected-pillars:
- Reliability
- Performance
- Operational Excellence triggers:
- "How do I route a Case to an agent with specific skills using Apex in Omni-Channel"
- "PendingServiceRouting IsReadyForRouting sequence — what order do I insert records"
- "Skills-based routing Apex — SkillRequirement records not being matched"
- "Custom routing logic for Omni-Channel with skill relaxation overflow"
- "ServiceChannelId hardcoded in Apex breaks after sandbox refresh" tags:
- omni-channel
- routing
- skills-based-routing
- PendingServiceRouting
- SkillRequirement
- service-cloud inputs:
- Service Channel Id (query by DeveloperName, never hardcoded)
- List of required Skill Ids and minimum skill levels
- Work item record Id (Case, Lead, or custom SObject)
- Routing priority and capacity weight outputs:
- Inserted PendingServiceRouting record with associated SkillRequirement records
- IsReadyForRouting flipped to true to activate routing
- Guidance on skill relaxation overflow configuration dependencies: [] version: 1.0.0 author: Pranav Nagrecha updated: 2026-04-06
Omni-Channel Custom Routing (Apex)
This skill activates when a team needs programmatic control over how work items are matched to agents via Omni-Channel's skills-based routing engine — specifically when Apex code must construct PendingServiceRouting records, associate SkillRequirement records, and trigger routing by flipping the IsReadyForRouting flag.
Before Starting
Gather this context before working on anything in this domain:
- Confirm that Omni-Channel and Skills-Based Routing are enabled in the org (Setup > Omni-Channel Settings). The
PendingServiceRoutingandSkillRequirementobjects are only available when the feature is active. - Identify the
ServiceChannelDeveloperNamevalues that correspond to the work item type — never hardcodeIdvalues, which differ across sandboxes and production. - Confirm the
SkillDeveloperNamevalues for each skill the routing logic requires. Query them at runtime rather than assuming they are stable. - Understand whether skill relaxation (overflow) is needed: when no agent with all required skills is available, the platform can drop
IsAdditionalSkill=trueskill requirements after a configured timeout.
Core Concepts
The Three-DML Sequence
Custom routing via Apex requires exactly three discrete DML operations in the correct order. Collapsing them fails silently or throws DML exceptions:
INSERTaPendingServiceRoutingrecord withRoutingType = 'SkillsBased'andIsReadyForRouting = false. The record must reference a validServiceChannelIdand theWorkItemIdof the case, chat, or custom object being routed.INSERTone or moreSkillRequirementrecords, each linked to thePendingServiceRoutingviaRelatedRecordId. Each record specifies aSkillId, aSkillLevel(1–10), and optionallyIsAdditionalSkill = trueto mark it as relaxable overflow.UPDATEthePendingServiceRoutingrecord, settingIsReadyForRouting = true. This signals to the routing engine that the work item is ready to be dispatched.
Attempting to set IsReadyForRouting = true on insert, or inserting SkillRequirement records before the parent PendingServiceRouting record exists, causes runtime errors.
SkillRequirement and Skill Relaxation
Each SkillRequirement record carries a SkillId, a SkillLevel (minimum proficiency 1–10), and a boolean IsAdditionalSkill. Skills marked IsAdditionalSkill = false are required — an agent must possess them at or above the minimum level. Skills marked IsAdditionalSkill = true are "nice to have" and are subject to relaxation: after the org-configured timeout elapses with no matching agent found, the routing engine drops all IsAdditionalSkill = true requirements and retries with only the required skills. This mechanism is the primary overflow strategy for skills-based routing.
ServiceChannelId Resolution
ServiceChannel.Id is an org-specific value. It changes between sandbox refreshes and between sandbox and production. Code that hardcodes a ServiceChannelId breaks silently on deployment — the insert succeeds but the routing engine ignores the record or throws a runtime error. The only safe pattern is to query ServiceChannel by DeveloperName at runtime. Cache the result in a custom metadata type or platform cache when query volume is a concern, but never embed the raw Id in Apex.
Avoiding SOQL in Loops for Skill Resolution
Each PendingServiceRouting record requires one or more Skill Ids. If the routing logic processes a batch of work items, querying Skill by DeveloperName inside the loop issues one SOQL call per record, exhausting governor limits. The correct pattern is to collect all required DeveloperName values first, issue a single SELECT Id, DeveloperName FROM Skill WHERE DeveloperName IN :names query, then build a Map<String, Id> for use within the loop.
Common Patterns
Pattern: Bulk Skills-Based Routing from a Trigger or Batch Job
When to use: A trigger fires on Case insert (or a batch job processes a queue of cases) and each case needs to be routed to an agent with matching skills.
How it works:
- Collect all
ServiceChannelandSkillDeveloperNamevalues needed by the batch. - Issue one
SELECTforServiceChanneland one forSkill— build lookup maps. - For each work item, construct and insert a
PendingServiceRoutingrecord withIsReadyForRouting = false. - Build a list of
SkillRequirementrecords linked byRelatedRecordId. INSERTallSkillRequirementrecords in one DML statement.- Set
IsReadyForRouting = trueon eachPendingServiceRoutingandUPDATEthe list.
Why not the alternative: A single-record approach (insert PSR, insert SR, flip flag, one at a time) works in low-volume scenarios but hits DML governor limits in batch contexts. Bulkifying all three DML steps avoids this.
Pattern: Skill Relaxation for Overflow
When to use: The org requires a best-effort agent match — if no agent has all skills, route to any agent with the core skill after a timeout.
How it works: Mark secondary or specialist skills with IsAdditionalSkill = true on the SkillRequirement record. The timeout is configured in the routing configuration's overflow settings. The routing engine automatically drops the IsAdditionalSkill = true requirements after the timeout and re-evaluates.
Why not the alternative: Manually creating a second PendingServiceRouting record after a timeout requires a scheduled job or platform event listener — more moving parts and harder to debug than the built-in relaxation mechanism.
Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Work items always need all skills matched | IsAdditionalSkill = false on all SkillRequirement records | No relaxation; agent must meet full requirement |
| Need overflow routing after timeout | Add secondary skills with IsAdditionalSkill = true | Platform handles relaxation without custom timer logic |
| Routing multiple work items in one transaction | Bulkify all three DML steps | Prevents DML governor limit breaches |
| ServiceChannel lookup needed | Query by DeveloperName at runtime | Id is org-specific and changes on sandbox refresh |
| Skill Ids needed for multiple records | Single bulk SOQL + Map<String, Id> | Avoids SOQL-in-loop governor limit |
| Want to cancel routing | Delete the PendingServiceRouting record | Deletion removes the work item from the routing queue |
Recommended Workflow
Step-by-step instructions for an AI agent or practitioner working on this task:
- Verify feature enablement — Confirm Omni-Channel and Skills-Based Routing are active in Setup. Confirm the
ServiceChannelandSkillrecords exist with knownDeveloperNamevalues. - Gather Ids safely — Query
ServiceChannelandSkillbyDeveloperNameat the top of the transaction. Store results in maps. Do not issue these queries inside loops. - Insert PendingServiceRouting records (IsReadyForRouting = false) — Construct and insert all
PendingServiceRoutingrecords in one DML operation. SetRoutingType = 'SkillsBased',IsReadyForRouting = false, and provideWorkItemId,ServiceChannelId,RoutingPriority, andCapacityWeight. - Insert SkillRequirement records — For each
PendingServiceRoutingrecord, constructSkillRequirementrecords linked viaRelatedRecordId. SetIsAdditionalSkillappropriately for required vs. overflow skills. Insert all in one DML statement. - Flip IsReadyForRouting = true — Update all
PendingServiceRoutingrecords to setIsReadyForRouting = truein a single DML update. This activates routing. - Handle errors and rollback — Wrap the sequence in a try/catch. On failure, delete any inserted
PendingServiceRoutingrecords to avoid orphaned routing entries that hold work items in a pending state indefinitely. - Validate in a sandbox with real agent availability — Routing only works when at least one agent with matching skills and capacity is present in the Omni-Channel utility. Test with a live agent before deploying to production.
Review Checklist
Run through these before marking work in this area complete:
-
ServiceChannelIdis resolved by queryingDeveloperName— no hardcoded Id in Apex -
SkillIds are resolved by a single bulk SOQL query, not queried per record - Three-DML sequence is preserved: insert PSR (flag=false) → insert SkillRequirements → update PSR (flag=true)
-
IsReadyForRouting = falseis set on insert,trueis set only afterSkillRequirementrecords exist - Overflow skills are marked
IsAdditionalSkill = trueif relaxation is intended - Error handling deletes
PendingServiceRoutingrecords on failure to prevent orphans - Tested in a sandbox with a real agent who has matching skills assigned
Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
- IsReadyForRouting on insert causes immediate but incomplete routing — Setting
IsReadyForRouting = trueduring the initial insert, beforeSkillRequirementrecords exist, causes the routing engine to evaluate the work item with zero skill requirements. This may silently route to the wrong agent or cause a DML exception, depending on org configuration. - ServiceChannelId hardcoding breaks on sandbox refresh — Salesforce reassigns record Ids when a sandbox is refreshed from production. Hardcoded
ServiceChannelIdvalues in Apex produce silent routing failures — the insert succeeds but the routing engine cannot find the channel. - Orphaned PendingServiceRouting blocks re-routing — A work item can only have one active
PendingServiceRoutingrecord at a time. If an exception leaves an orphaned record (inserted but never deleted), subsequent routing attempts fail with a DUPLICATE_VALUE error. Clean up on failure.
Output Artifacts
| Artifact | Description |
|---|---|
PendingServiceRouting record | The routing ticket created by Apex; links the work item to a service channel and holds routing parameters |
SkillRequirement records | Per-skill requirements associated with the PendingServiceRouting record; define which agent skills and levels are needed |
| Routing outcome | Work item assigned to a qualified agent's Omni-Channel queue when a match is found |
Related Skills
architect/omni-channel-capacity-model— use alongside this skill when tuning agent capacity weights and channel throughputadmin/sales-engagement-cadences— if routing work items generated by cadence steps, coordinate channel setup with this skill