Salesforce has never had a built-in Apex way to fetch a field's picklist values filtered to a specific Record Type — Schema.DescribeFieldResult.getPicklistValues() only returns the field's global active values, ignoring any per-record-type restrictions configured in Setup. This package closes that gap with two small, generic Apex classes, with no dependency on any specific object, field, or org.
| Class | Purpose |
|---|---|
PicklistValuesByRecordType |
Bulk-safe @InvocableMethod — callable from Flow, Process Builder, or directly via the REST Invocable Actions API. |
PicklistValuesByRecordTypeCallable |
A Callable-interface adapter for invoking the same logic from OmniStudio Integration Procedure / OmniScript "Remote Action" elements. |
Both classes use ConnectApi.RecordUi.getPicklistValuesByRecordType internally (a native Apex method available from Spring '26 onward) — no callouts, no manual metadata parsing, and it correctly respects controlling-field/dependent-picklist restrictions for the given record type.
- API version 66.0+ (Spring '26 or later) — required for
ConnectApi.RecordUi.getPicklistValuesByRecordType. - Apex Class Access for both classes must be granted via a permission set to any user/profile that needs to invoke them (not required for system-context Flow execution).
Add an Action element, search for "Get Picklist Values By Record Type", and supply:
- Record Type Id — the
Idof the record type to filter by. - Field API Name — the picklist field's API name (e.g.
Industry,MyCustomField__c).
The action is bulk-safe: in a loop, each iteration is processed independently, and one invalid row doesn't fail the rest of the batch. The response includes:
- Is Success (
Boolean) - Error Message (
String, populated only whenIs Successisfalse) - Picklist Values (
Listof{label, value})
POST /services/data/v60.0/actions/custom/apex/PicklistValuesByRecordType
Content-Type: application/json
Authorization: Bearer <session token>
{
"inputs": [
{ "recordTypeId": "012000000000000AAA", "fieldApiName": "Industry" }
]
}
Response:
[
{
"actionName": "PicklistValuesByRecordType",
"isSuccess": true,
"outputValues": {
"isSuccess": true,
"errorMessage": null,
"picklistValues": [
{ "label": "Agriculture", "value": "Agriculture" },
{ "label": "Banking", "value": "Banking" }
]
}
}
]OmniStudio's Remote Action elements need an Apex class implementing the standard Callable interface, not a plain @InvocableMethod. Rather than wiring up structured "Additional Input"/Set Values mappings (which proved unreliable across different OmniStudio Designer versions in practice), this class takes a single composite string and resolves everything from it.
| Property | Value |
|---|---|
| Remote Class | PicklistValuesByRecordTypeCallable |
| Remote Method | SObjectType::RecordTypeDeveloperName::FieldApiName |
For example, to get the values of a custom picklist field MyCustomField__c on MyObject__c, restricted to the record type with developer name MyRecordType:
Remote Method: MyObject__c::MyRecordType::MyCustomField__c
The double-colon (::) delimiter is deliberate — Salesforce API names and Record Type developer names can contain underscores, so a single underscore would be ambiguous as a separator. Colons never appear in either, so splitting is always unambiguous.
The result is written into the Remote Action's output (surfaced under its Remote key in the Integration Procedure / OmniScript context):
{
"isSuccess": true,
"errorMessage": null,
"options": [
{ "name": "Agriculture", "value": "Agriculture" },
{ "name": "Banking", "value": "Banking" }
]
}Note on name/value orientation: some OmniStudio Select element configurations expect name to hold the underlying value and value to hold the display label — the reverse of what the key names suggest. This was confirmed empirically against a specific org's OmniStudio Select element and is encoded in PicklistValuesByRecordTypeCallable's serialize() method. If your org's Select elements expect the conventional orientation instead (name = display label, value = underlying value), swap the two arguments in that method.
Both test classes use @IsTest(SeeAllData=true). This is not optional and is not a sign of fragile test design: any Apex test exercising ConnectApi.* methods is required by the platform to use SeeAllData=true, regardless of what data the code actually touches. The tests here only ever read RecordType metadata (Setup configuration) and standard Account picklist fields (Industry) — never transactional business records — so this requirement doesn't introduce the usual risks associated with SeeAllData=true (fragile dependence on org data state).
sf project deploy start --source-dir force-app --test-level RunLocalTests
This is structured as an unlocked package. To create and install a version:
sf package version create --package "Picklist Values By Record Type" --installation-key-bypass --wait 20
sf package install --package <package-version-id> --target-org <your-org> --wait 10
Alternatively, deploy the source directly without packaging:
sf project deploy start --source-dir force-app --target-org <your-org>
After installing, grant Apex Class Access for both classes via a permission set to any non-admin user/profile that needs to invoke them directly (e.g. from a Screen Flow running in user context, or a Community/Experience Cloud user).