Picklists, multi-select picklists, and checkboxes
Most of the fields people actually filter on in Salesforce aren't free text — they're picklists, checkboxes, and record types. Each one looks simple in the UI and then behaves slightly differently in SOQL: picklists match on a hidden API value, multi-selects need their own operators, and booleans refuse quotes entirely. This chapter builds on filtering with WHERE and covers the rules for each of these field types.
Picklists are strings with rules
Under the hood, a picklist field is just a text field with a constrained set of values. You filter it
exactly like any other string — with =, !=, IN, and single
quotes:
SELECT Id, Name, Industry FROM Account WHERE Industry = 'Technology'
The catch is which string you compare against. Every picklist entry has two parts: a
label, which is what users see in the UI, and an API value, which is
what's actually stored on the record. SOQL filters on the API value. The two usually start out
identical, but they can drift apart: when an admin renames a label, Salesforce keeps the old API value
so existing records and integrations don't break. So a stage that displays as
"Negotiation" in the UI might still be stored as 'Negotiation/Review' — and that's the
value your query needs.
SELECT Id, Name, Amount FROM Opportunity WHERE StageName = 'Negotiation/Review'
When a picklist filter mysteriously returns zero rows, check the real values first: in Setup, open Object Manager, click the object, open the field, and look at the Values section. The API value column is what belongs in your quotes.
toLabel() for display
Querying raw API values is correct for filtering, but it's not always what you want to show people —
especially in orgs with translated picklists, where the stored value is one language and the user sees
another. The toLabel() function converts a picklist field to its label, translated for
the running user:
SELECT Id, Name, toLabel(Rating) FROM Account WHERE Rating != null
Without toLabel(), a German user querying Rating gets the raw value
'Hot'; with it, they get the translated label their org configured. Use the raw field
when you need to compare or match values in code, and toLabel() when the result is headed
for human eyes.
Multi-select picklists
A multi-select picklist stores every chosen value in a single field, joined with semicolons. If a
contact's interests are Golf and Tennis, the field holds the string 'Golf;Tennis'. That
storage format drives everything about how you query it.
Plain = still works, but it matches the exact, complete selection — nothing more,
nothing less. To ask "is this value among the selections", SOQL gives multi-selects two dedicated
operators: INCLUDES and EXCLUDES.
The simplest form finds anyone whose interests include Golf, alone or alongside anything else:
SELECT Id, Name, Interests__c
FROM Contact
WHERE Interests__c INCLUDES ('Golf')
A semicolon inside a single entry means AND — Golf and Tennis both selected:
SELECT Id, Name
FROM Contact
WHERE Interests__c INCLUDES ('Golf;Tennis')
A comma between entries means OR — Golf or Tennis selected:
SELECT Id, Name
FROM Contact
WHERE Interests__c INCLUDES ('Golf', 'Tennis')
| Filter | Matches records where… |
|---|---|
Interests__c = 'Golf;Tennis' | The selection is exactly Golf and Tennis — no other values. |
INCLUDES ('Golf') | Golf is among the selections, whatever else is picked. |
INCLUDES ('Golf;Tennis') | Golf and Tennis are both selected together. |
INCLUDES ('Golf', 'Tennis') | At least one of Golf or Tennis is selected. |
EXCLUDES ('Golf') | Golf is not among the selections. |
EXCLUDES is the mirror image of INCLUDES: the same semicolon-and-comma rules
apply, negated. Multi-select API values follow the same label-versus-value rule as regular picklists,
so verify them in Object Manager too.
Checkboxes (booleans)
Checkbox fields hold true or false, and in SOQL you write those values bare —
no quotes, ever:
SELECT Id, Subject, Status FROM Case WHERE IsClosed = false AND IsEscalated = true
Writing IsClosed = 'false' compares a boolean to a string and fails. The other thing that
sets checkboxes apart: they are never null. An unchecked box is stored as
false, full stop — even on records created before the field existed. So there's no need
for the null checks you'd use on other field types; = true and = false
between them cover every record. Standard objects ship with useful checkboxes everywhere:
IsClosed and IsWon on Opportunity, IsActive on User and
Product2, HasOptedOutOfEmail on Contact.
Record types
When one object wears multiple hats — say, Account records that are either customers or partners —
orgs use record types to tell them apart. Every record carries a
RecordTypeId, and you can hop across that relationship to filter by the record type's
name:
SELECT Id, Name, AnnualRevenue FROM Account WHERE RecordType.DeveloperName = 'Partner_Account'
That RecordType. dot-walk is a small preview of
child-to-parent queries, coming up shortly.
The field to prefer is DeveloperName — the record type's API name. Like a picklist API
value, it survives renames: an admin can relabel "Partner Account" to "Channel Partner" tomorrow and
RecordType.Name filters break, while DeveloperName keeps matching.
The alternative is filtering on RecordTypeId directly, which skips the relationship hop.
It works, but the id differs between orgs — a query hardcoded against a sandbox id won't match in
production — so reach for DeveloperName unless you have a specific reason not to.
You now have the full toolkit for filtering Salesforce's constrained field types. Next, the tutorial turns to the feature that makes SOQL genuinely different from SQL: relationships.