The multi-select picklist that could not survive its own success

A multi-select picklist caps at 150 values, cannot be rolled up, joined or filtered properly, and there are roughly 390 US metro areas. Replacing one with a junction object, seeded from the Census delineation file.

When I built the Account layer for Doorvana, one of the fields was Markets__c — a multi-select picklist on an existing global value set of Texas metro areas, so a builder or dealer could be tagged with everywhere it operates.

It deployed clean. It was also wrong, and the reason is worth more than the fix.

Multi-select picklists are a reporting dead end

The numeric limits are the least of it. A multi-select picklist tops out at 150 active values and 100 selected on any one record. Doorvana’s list was 29 metros, comfortably inside both.

The real problems are structural:

  • You cannot roll up a multi-select. There is no roll-up summary that counts how many accounts cover a given metro, because there is no child record to count.
  • You cannot join on it. Nothing else in the org can relate to a picklist value.
  • Filtering is miserable. includes and excludes exist, but grouping a report by a multi-select produces a row per unique combination — “Dallas;Fort Worth;Austin” is one group, “Austin;Dallas” is another. That is not a report anyone reads twice.
  • The values carry nothing. A metro area has a code, a state, and a lifecycle. A picklist value is a string.

So the first question anyone will ask of this data — which dealers cover Houston? — has no clean answer.

The ceiling was closer than it looked

The value set was 29 metros, all Texas. That felt safe next to a 150 limit.

Except that 29 is the count of Texas metros, not the count of metros. There are 393 metropolitan statistical areas in the United States. The day Doorvana serves a dealer in Oklahoma or Louisiana, that list grows toward a number a multi-select picklist physically cannot hold. The cap is not a distant theoretical limit; it is roughly two and a half times the real-world list size.

What replaced it

A Market__c object for the metro areas, and an Account_Market__c junction joining accounts to them.

The picklist stored a string on the Account and nothing could relate to it. The junction gives every account-to-market pairing its own record, so roll-up summaries count covering accounts, reports group cleanly by market, and each market carries its own CBSA code, state and active flag.

Two master-detail relationships rather than lookups, with Account as the primary. That choice buys two things: the junction inherits Account’s sharing rather than needing its own model, and a roll-up summary on Market__c can count covering accounts — which was the whole point.

force-app/main/default/objects/Account_Market__c/fields/Account__c.field-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Account__c</fullName>
    <label>Account</label>
    <type>MasterDetail</type>
    <referenceTo>Account</referenceTo>
    <relationshipLabel>Markets</relationshipLabel>
    <relationshipName>Account_Markets</relationshipName>
    <relationshipOrder>0</relationshipOrder>
    <reparentableMasterDetail>false</reparentableMasterDetail>
</CustomField>

relationshipOrder is 0, which is what makes Account the primary master. It is one line and it decides the sharing model.

The permission set trap

The first deploy of the junction failed, and the error was not obvious.

A related one, for the same file: required fields take no fieldPermissions entry at all. Market_Code__c is required, so it has none — and adding one to “fix” a perceived gap makes the deploy fail.

Seeding it with real codes, not invented ones

An empty object is not a migration. The markets had to come from somewhere, and the somewhere matters.

Market_Code__c holds CBSA codes pulled from the Census Bureau’s 2023 delineation file. All 26 active Texas metros in the old value set matched the delineation by exact title. The two inactive values resolved to renamed successors:

Old value2023 delineationCBSA
Houston-The Woodlands-Sugar LandHouston-Pasadena-The Woodlands26420
Austin-Round RockAustin-Round Rock-San Marcos12420

That is the argument for codes, made concretely. Two of twenty-eight names changed inside a few years. Anything keyed on the name would have broken; the codes did not move.

Other - Out of Area has no CBSA equivalent, so it carries the synthetic code OTHER — the one row where the field is not a real CBSA code, which is worth writing down before somebody assumes otherwise.

27 records upserted on Market_Code__c as the external ID.

Two loading gotchas

the error that wastes twenty minutes

ERROR running force:data:bulk:upsert:
LineEnding is invalid on user data. Expecting LF but found CRLF

The CLI declares LF to the Bulk API regardless of what is actually in the file. A CSV saved with Windows line endings fails with a message that sounds like a data problem and is not. Convert to LF and it loads unchanged.

The second: deploying a global value set with values removed is additive. Deleting values from the XML does not delete them from the org. The two renamed metros had to be deployed as isActive=false rather than removed — the value set ends up with 29 values, two inactive, which is the honest representation anyway since historical records may reference them.

What it cost, and what it bought

The rebuild was one object, one junction, three fields, a permission set edit, and a data load. Call it an afternoon.

What it bought: which dealers cover Houston is now a report rather than a project. Markets can be retired with the Active__c flag without deleting history. And when Doorvana’s coverage crosses a state line, the list grows past 150 without anybody having to redesign anything under pressure.

The original field is marked superseded rather than deleted, because the sandbox has no Account records to migrate and there was no reason to be dramatic about it. If it had held real data, this would have been a migration rather than an afternoon — which is the actual lesson. The multi-select was not wrong because it was badly built. It was wrong because it was the kind of decision that gets more expensive every week it survives.

Filed under

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. More on the work page.

FAQ

What is the limit on a Salesforce multi-select picklist?

A multi-select picklist allows a maximum of 150 active values, and a single record can hold up to 100 of them. The harder limits are not numeric: multi-select values cannot be rolled up with a roll-up summary field, cannot be joined to another object, and filter behaviour in reports is awkward enough that most teams give up and export to a spreadsheet.

When should a multi-select picklist be replaced with a junction object?

Replace it as soon as the values need to be reported on, rolled up, joined, or owned as records in their own right. A junction object gives each pairing its own row, which means roll-up summaries work, reports group cleanly, and each value can carry its own fields such as a code, a state, or an active flag. A multi-select picklist is fine for a small, stable list nobody reports across.

Why does granting read on a master-detail child in a permission set fail?

Read access on a master-detail child object requires read access on the master object in that same permission set. Splitting them across two permission sets fails validation. If a permission set grants access to a junction object, it has to grant read on both parents as well.

Why does a Salesforce Bulk API data load fail with LineEnding is invalid on user data?

The Salesforce CLI declares LF line endings to the Bulk API regardless of what the file actually contains, so a CSV saved with Windows CRLF line endings is rejected with that error. Convert the file to LF before loading. Nothing about the data itself is wrong, which is what makes the message misleading.

Should a Salesforce picklist of metro areas store names or codes?

Store the code and treat the name as a label. Metropolitan statistical area names change: the 2023 Census delineation renamed Houston-The Woodlands-Sugar Land to Houston-Pasadena-The Woodlands and Austin-Round Rock to Austin-Round Rock-San Marcos. A record keyed on the name breaks at the rename; one keyed on the CBSA code does not.

Working on something like this?

I can tell you what it takes.

If you run a home services business and this looks like a problem you have, book a call and we will talk through it.